Skip to content

Commit

Permalink
feat(EMI-2146): Add Task onboarding animation (#11031)
Browse files Browse the repository at this point in the history
* feat(EMI-2146): Add Task onboarding animation

* add reanimated swipeable patch

* prevent onboarding overlapp
  • Loading branch information
MrSltun authored Oct 30, 2024
1 parent 18535b5 commit 4181b1b
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 86 deletions.
11 changes: 11 additions & 0 deletions HACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,14 @@ https://github.com/fastlane/fastlane/pull/22025
We want to be able to promote past android builds to prod because we are creating betas often and a release candidate may not be
the latest. The developer APIs for google play only return the latest release and fastlane verifies that a release exists before allowing
promotion. We added custom logic to work around this.

## react-native-gesture-handler/ReanimatedSwipeable patch

#### When we can remove this:

When this pr is released in a new version of react-native-gesture-handler and we upgrade to it:
https://github.com/software-mansion/react-native-gesture-handler/pull/3149

#### Explanation/Context:

We want to be able to open the swipeable view using the `openRight` function, but it doesn't seem to be working as expected. This patch is a workaround to make it work.
13 changes: 13 additions & 0 deletions patches/react-native-gesture-handler+2.19.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/node_modules/react-native-gesture-handler/src/components/ReanimatedSwipeable.tsx b/node_modules/react-native-gesture-handler/src/components/ReanimatedSwipeable.tsx
index 40e760c..9e226a2 100644
--- a/node_modules/react-native-gesture-handler/src/components/ReanimatedSwipeable.tsx
+++ b/node_modules/react-native-gesture-handler/src/components/ReanimatedSwipeable.tsx
@@ -444,7 +444,7 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
openRight() {
'worklet';
rightWidth.value = rowWidth.value - rightOffset.value;
- animateRow(calculateCurrentOffset(), -rightWidth.value);
+ animateRow(calculateCurrentOffset(), -(rowWidth.value - rightOffset.value));
},
reset() {
'worklet';
14 changes: 6 additions & 8 deletions src/app/Components/Swipeable/Swipeable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Color, Flex, Touchable, useColor } from "@artsy/palette-mobile"
import { forwardRef, useRef } from "react"
import ReanimatedSwipeable, {
SwipeableMethods,
SwipeableRef,
SwipeableProps,
} from "react-native-gesture-handler/ReanimatedSwipeable"
import Animated, {
runOnJS,
Expand All @@ -14,13 +14,11 @@ import Animated, {
const FRICTION = 1.2
const SWIPE_TO_INTERACT_THRESHOLD = 80

export interface SwipeableProps {
children: React.ReactNode
export interface SwipeableComponentProps extends SwipeableProps {
actionOnPress?: () => void
actionOnSwipe?: () => void
actionComponent: React.ReactNode
actionBackground?: Color
swipeableProps?: SwipeableProps
enabled?: boolean
/**
* The width of the action component. We need to set it to make the swipeable animation.\
Expand All @@ -29,7 +27,7 @@ export interface SwipeableProps {
actionComponentWidth: number
}

export const Swipeable = forwardRef((props: SwipeableProps, swipeableRef: SwipeableRef) => {
export const Swipeable = forwardRef<SwipeableMethods, SwipeableComponentProps>((props, ref) => {
const {
children,
actionOnPress,
Expand All @@ -38,7 +36,7 @@ export const Swipeable = forwardRef((props: SwipeableProps, swipeableRef: Swipea
actionComponentWidth,
actionBackground = "red100",
enabled = true,
swipeableProps,
...restProps
} = props

const color = useColor()
Expand Down Expand Up @@ -111,11 +109,11 @@ export const Swipeable = forwardRef((props: SwipeableProps, swipeableRef: Swipea
>
<ReanimatedSwipeable
testID="swipeable-component"
ref={swipeableRef}
ref={ref}
enabled={enabled}
renderRightActions={RightActions}
friction={FRICTION}
{...swipeableProps}
{...restProps}
>
{children}
</ReanimatedSwipeable>
Expand Down
137 changes: 66 additions & 71 deletions src/app/Components/Tasks/Task.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { useHomeViewTracking } from "app/Scenes/HomeView/useHomeViewTracking"
import { navigate } from "app/system/navigation/navigate"
import { useAcknowledgeTask } from "app/utils/mutations/useAcknowledgeTask"
import { useDismissTask } from "app/utils/mutations/useDismissTask"
import { useRef } from "react"
import { forwardRef } from "react"
import { PixelRatio } from "react-native"
import { SwipeableMethods } from "react-native-gesture-handler/lib/typescript/components/ReanimatedSwipeable"
import { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"
import { graphql, useFragment } from "react-relay"

const TASK_IMAGE_SIZE = 60
Expand All @@ -19,84 +19,79 @@ interface TaskProps {
onPress?: () => void
task: Task_task$key
}
export const Task: React.FC<TaskProps> = ({
disableSwipeable,
onClearTask,
onPress,
...restProps
}) => {
const { tappedNotification, tappedClearNotification } = useHomeViewTracking()
const { submitMutation: dismissTask } = useDismissTask()
const { submitMutation: acknowledgeTask } = useAcknowledgeTask()
const fontScale = PixelRatio.getFontScale()
export const Task = forwardRef<SwipeableMethods, TaskProps>(
({ disableSwipeable, onClearTask, onPress, ...restProps }, ref) => {
const { tappedNotification, tappedClearNotification } = useHomeViewTracking()
const { submitMutation: dismissTask } = useDismissTask()
const { submitMutation: acknowledgeTask } = useAcknowledgeTask()
const fontScale = PixelRatio.getFontScale()

const task = useFragment(taskFragment, restProps.task)
const task = useFragment(taskFragment, restProps.task)

const handlePressTask = () => {
if (onPress) {
onPress()
return
}

acknowledgeTask({ variables: { taskID: task.internalID } })
tappedNotification(ContextModule.actNow, task.actionLink, task.internalID, task.taskType)
onClearTask()
const handlePressTask = () => {
if (onPress) {
onPress()
return
}

navigate(task.actionLink)
}
acknowledgeTask({ variables: { taskID: task.internalID } })
tappedNotification(ContextModule.actNow, task.actionLink, task.internalID, task.taskType)
onClearTask()

const handleClearTask = async () => {
dismissTask({ variables: { taskID: task.internalID } })
tappedClearNotification(ContextModule.actNow, task.actionLink, task.internalID, task.taskType)
onClearTask()
}
navigate(task.actionLink)
}

const swipeableRef = useRef<SwipeableMethods>(null)
const handleClearTask = async () => {
dismissTask({ variables: { taskID: task.internalID } })
tappedClearNotification(ContextModule.actNow, task.actionLink, task.internalID, task.taskType)
onClearTask()
}

return (
<Swipeable
actionComponent={
<Text variant="xs" color="white100">
Clear
</Text>
}
actionComponentWidth={80 * fontScale}
actionOnPress={handleClearTask}
actionOnSwipe={handleClearTask}
enabled={!disableSwipeable}
ref={swipeableRef}
>
<Flex backgroundColor="white100" borderRadius={5}>
<Touchable onPress={handlePressTask}>
<Flex
p={1}
ml={2}
flexDirection="row"
border="1px solid"
borderColor="black100"
borderRadius={5}
zIndex={3}
backgroundColor="black100"
>
<Flex pr={1}>
<Image src={task.imageUrl} width={TASK_IMAGE_SIZE} height={TASK_IMAGE_SIZE} />
</Flex>
return (
<Swipeable
ref={ref}
actionComponent={
<Text variant="xs" color="white100">
Clear
</Text>
}
actionComponentWidth={80 * fontScale}
actionOnPress={handleClearTask}
actionOnSwipe={handleClearTask}
enabled={!disableSwipeable}
>
<Flex backgroundColor="white100" borderRadius={5}>
<Touchable onPress={handlePressTask}>
<Flex
p={1}
ml={2}
flexDirection="row"
border="1px solid"
borderColor="black100"
borderRadius={5}
zIndex={3}
backgroundColor="black100"
>
<Flex pr={1}>
<Image src={task.imageUrl} width={TASK_IMAGE_SIZE} height={TASK_IMAGE_SIZE} />
</Flex>

<Flex flex={1} pr={1}>
<Text color="white100" variant="xs" fontWeight="bold">
{task.title}
</Text>
<Flex flex={1} pr={1}>
<Text color="white100" variant="xs" fontWeight="bold">
{task.title}
</Text>

<Text color="white100" variant="xs">
{task.message}
</Text>
<Text color="white100" variant="xs">
{task.message}
</Text>
</Flex>
</Flex>
</Flex>
</Touchable>
</Flex>
</Swipeable>
)
}
</Touchable>
</Flex>
</Swipeable>
)
}
)

const taskFragment = graphql`
fragment Task_task on Task {
Expand Down
44 changes: 37 additions & 7 deletions src/app/Scenes/HomeView/Sections/HomeViewSectionTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { SectionTitle } from "app/Components/SectionTitle"
import { Task } from "app/Components/Tasks/Task"
import { HomeViewSectionSentinel } from "app/Scenes/HomeView/Components/HomeViewSectionSentinel"
import { SectionSharedProps } from "app/Scenes/HomeView/Sections/Section"
import { GlobalStore } from "app/store/GlobalStore"
import { extractNodes } from "app/utils/extractNodes"
import { NoFallback, withSuspense } from "app/utils/hooks/withSuspense"
import { useState } from "react"
import { LayoutAnimation } from "react-native"
import { useEffect, useRef, useState } from "react"
import { InteractionManager, LayoutAnimation } from "react-native"
import { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"
import { graphql, useFragment, useLazyLoadQuery } from "react-relay"

interface HomeViewSectionTasksProps extends FlexProps {
Expand All @@ -26,29 +28,57 @@ export const HomeViewSectionTasks: React.FC<HomeViewSectionTasksProps> = ({

const tasks = extractNodes(section.tasksConnection)

const swipeableRef = useRef<SwipeableMethods>(null)
const {
isDismissed,
sessionState: { isReady },
} = GlobalStore.useAppState((state) => state.progressiveOnboarding)
const { dismiss } = GlobalStore.actions.progressiveOnboarding

// adding the find-saved-artwork onboarding key to prevent overlap
const shouldStartOnboardingAnimation =
isReady && !isDismissed("act-now-tasks").status && !!isDismissed("find-saved-artwork").status

// In the future, we may want to show multiple tasks
const task = tasks?.[0]

const [displayTask, setDisplayTask] = useState(true)

useEffect(() => {
if (shouldStartOnboardingAnimation) {
startOnboardingAnimation()
}
}, [shouldStartOnboardingAnimation])

if (!task || !displayTask) {
return null
}

const startOnboardingAnimation = () => {
InteractionManager.runAfterInteractions(() => {
swipeableRef.current?.openRight()
}).then(() => {
setTimeout(() => {
swipeableRef.current?.close()
dismiss("act-now-tasks")
}, 1500)
})
}

const handleClearTask = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)

setDisplayTask(false)
}

if (!task || !displayTask) {
return null
}

return (
<Flex {...flexProps}>
<Flex mx={2}>
<SectionTitle title={section.component?.title} />
</Flex>

<Flex mr={2}>
<Task task={task} onClearTask={handleClearTask} />
<Task ref={swipeableRef} task={task} onClearTask={handleClearTask} />
</Flex>

<HomeViewSectionSentinel
Expand Down
4 changes: 4 additions & 0 deletions src/app/store/ProgressiveOnboardingModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ export const PROGRESSIVE_ONBOARDING_PARTNER_OFFER_CHAIN = [
PROGRESSIVE_ONBOARDING_OFFER_SETTINGS,
]

// Act Now Tasks
export const PROGRESSIVE_ONBOARDING_ACT_NOW_TASKS = "act-now-tasks"

export const PROGRESSIVE_ONBOARDING_KEYS = [
PROGRESSIVE_ONBOARDING_MY_COLLECTION_SELL_THIS_WORK,
PROGRESSIVE_ONBOARDING_SAVE_ARTWORK,
Expand All @@ -104,6 +107,7 @@ export const PROGRESSIVE_ONBOARDING_KEYS = [
PROGRESSIVE_ONBOARDING_ALERT_FINISH,
PROGRESSIVE_ONBOARDING_SIGNAL_INTEREST,
PROGRESSIVE_ONBOARDING_OFFER_SETTINGS,
PROGRESSIVE_ONBOARDING_ACT_NOW_TASKS,
] as const

export type ProgressiveOnboardingKey = (typeof PROGRESSIVE_ONBOARDING_KEYS)[number]

0 comments on commit 4181b1b

Please sign in to comment.