Skip to content

Commit

Permalink
feat(EMI-2217): Animate tasks on tap or clear (#11325)
Browse files Browse the repository at this point in the history
* add reanimated patch

* feat(EMI-2217): Animate tasks on tap or clear

* update some HACKS.md headers

* fix android specific bug with reanimated
  • Loading branch information
MrSltun authored Dec 27, 2024
1 parent fcdb93a commit 9f6d733
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 31 deletions.
25 changes: 18 additions & 7 deletions HACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Doesn't really need to be removed but can be if view hierarchy issue is fixed in

We have a modal for showing a loading state and a onDismiss call that optionally displays an alert message, on iOS 14 we came across an issue where the alert was not displaying because when onDismiss was called the LoadingModal was still in the view hierarchy. The delay is a workaround.

# android Input placeholder measuring hack
## android Input placeholder measuring hack

#### When can we remove this:

Expand All @@ -62,7 +62,7 @@ As you can see in the PR and issue, android doesn't use ellipsis on the placehol

We added a workaround on Input, to accept an array of placeholders, from longest to shortest, so that android can measure which one fits in the TextInput as placeholder, and it uses that. When android can handle a long placeholder and use ellipsis or if we don't use long placeholders anymore, this can go.

# `react-native-screens` fragment crash on open from background on Android
## `react-native-screens` fragment crash on open from background on Android

#### When can we remove this:

Expand All @@ -87,7 +87,7 @@ I wasn't the one to add this file, so I don't have all the context, but I do kno

The latest change I did was add the `ThemeContext` in there, because the version of styled-components we use has that, but the types are not exposing that, so I had to manually add it there.

# `react-native-push-notification` Requiring unknown module on ios
## `react-native-push-notification` Requiring unknown module on ios

#### When can we remove this:

Expand All @@ -99,7 +99,7 @@ This is happening because react-native-push-notification requires @react-native-
adding this dependency at this time because it is unnecessary and we do not use react-native-push-notification on iOS. Also,
we do not want unnecessary conflicts between our native push notification implementation and @react-native-community/push-notification-ios's.

# `PropsStore` pass functions as props inside navigate() on iOS
## `PropsStore` pass functions as props inside navigate() on iOS

#### When can we remove this:

Expand All @@ -113,7 +113,7 @@ See what can be converted: https://github.com/facebook/react-native/blob/main/Re

PropsStore allows us to temporarily hold on the props and reinject them back into the destination view or module.

# `ORStackView` patch (add UIKit import)
## `ORStackView` patch (add UIKit import)

#### When can we remove this:

Expand All @@ -123,7 +123,7 @@ Once we remove ORStackView or the upstream repo adds the import. May want to pro

The Pod does not compile when imported as is without hack due to missing symbols from UIKit.

# `Map` manual prop update in `PageWrapper`
## `Map` manual prop update in `PageWrapper`

#### When can we remove this:

Expand All @@ -135,7 +135,7 @@ If it is still an issue with old native navigation gone this can either be remov
City Guide is a mixture of native components and react components, prop updates from the native side are not updating the component on the react native side without this manual check and update. See the PR here for the change in the AppRegistry:
https://github.com/artsy/eigen/pull/6348

# `React-Native-Image-Crop-Picker` App restarting when photo is taken. Fix is in `ArtsyNativeModule.clearCache`.
## `React-Native-Image-Crop-Picker` App restarting when photo is taken. Fix is in `ArtsyNativeModule.clearCache`.

#### When can we remove this:

Expand Down Expand Up @@ -285,3 +285,14 @@ After updates to both/either react native react-native-collapsible-tab-view. Rem
#### Explanation/Context:

After upgrading to react native 0.75 screens like my collection using this library stopped rendering on Android. This was fixed with a patch that added some style changes to the components from the package.

## patch-pacakge for react-native-reanimated

#### When can we remove this:

When we can update the reanimated flatlist CellRendererComponent or it's style, or when this PR gets merged:
https://github.com/software-mansion/react-native-reanimated/pull/6573

#### Explanation/Context:

In the HomeView Tasks, we want to update the FlatList's `CellRendererComponent` to update the `zIndex` of the rendered elements so they can be on top of each other, and to animate them we need to use Reanimated's FlatList, but it doesn't support updating the `CellRendererComponent` prop since they have their own implementation, so we added this patch to update the style of the component in Reanimated's FlatList.
135 changes: 135 additions & 0 deletions patches/react-native-reanimated+3.16.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
diff --git a/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts b/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts
index a427011..9244d1b 100644
--- a/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts
+++ b/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts
@@ -1,7 +1,7 @@
import React from 'react';
-import type { FlatListProps } from 'react-native';
+import type { FlatListProps, StyleProp, ViewStyle } from 'react-native';
import { FlatList } from 'react-native';
-import type { ILayoutAnimationBuilder } from '../layoutReanimation/animationBuilder/commonTypes';
+import type { AnimatedStyle, ILayoutAnimationBuilder } from '../commonTypes';
import type { AnimatedProps } from '../helperTypes';
declare const AnimatedFlatList: React.ComponentClass<import("../helperTypes").AnimateProps<FlatListProps<unknown>>, any>;
interface ReanimatedFlatListPropsWithLayout<T> extends AnimatedProps<FlatListProps<T>> {
@@ -18,6 +18,14 @@ interface ReanimatedFlatListPropsWithLayout<T> extends AnimatedProps<FlatListPro
skipEnteringExitingAnimations?: boolean;
/** Property `CellRendererComponent` is not supported in `Animated.FlatList`. */
CellRendererComponent?: never;
+ /**
+ * Either animated view styles or a function that receives the item to be
+ * rendered and its index and returns animated view styles.
+ */
+ CellRendererComponentStyle?: StyleProp<AnimatedStyle<StyleProp<ViewStyle>>> | (({ item, index, }: {
+ item: T;
+ index: number;
+ }) => StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>) | undefined;
}
export type FlatListPropsWithLayout<T> = ReanimatedFlatListPropsWithLayout<T>;
interface AnimatedFlatListComplement<T> extends FlatList<T> {
diff --git a/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts.map b/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts.map
index afc2283..0c3ac1f 100644
--- a/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts.map
+++ b/node_modules/react-native-reanimated/lib/typescript/component/FlatList.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"FlatList.d.ts","sourceRoot":"","sources":["../../../src/component/FlatList.tsx"],"names":[],"mappings":"AACA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EACV,aAAa,EAId,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAC;AAGjG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,QAAA,MAAM,gBAAgB,0FAAoC,CAAC;AA4B3D,UAAU,iCAAiC,CAAC,CAAC,CAC3C,SAAQ,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IACvC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,uBAAuB,CAAC;IAC9C;;;OAGG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,KAAK,CAAC;CAC/B;AAED,MAAM,MAAM,uBAAuB,CAAC,CAAC,IAAI,iCAAiC,CAAC,CAAC,CAAC,CAAC;AAI9E,UAAU,0BAA0B,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IACzD,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;CACxB;AAgDD,eAAO,MAAM,kBAAkB;;MAQ1B,MAAM,YAAY,CAAC;AAExB,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,OAAO,gBAAgB,GACzD,0BAA0B,CAAC,CAAC,CAAC,CAAC"}
\ No newline at end of file
+{"version":3,"file":"FlatList.d.ts","sourceRoot":"","sources":["../../../src/component/FlatList.tsx"],"names":[],"mappings":"AACA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EACV,aAAa,EAEb,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,QAAA,MAAM,gBAAgB,0FAAoC,CAAC;AAyC3D,UAAU,iCAAiC,CAAC,CAAC,CAC3C,SAAQ,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IACvC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,uBAAuB,CAAC;IAC9C;;;OAGG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,KAAK,CAAC;IAC9B;;;OAGG;IACH,0BAA0B,CAAC,EACvB,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,GAC9C,CAAC,CAAC,EACA,IAAI,EACJ,KAAK,GACN,EAAE;QACD,IAAI,EAAE,CAAC,CAAC;QACR,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GACrD,SAAS,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,CAAC,CAAC,IAAI,iCAAiC,CAAC,CAAC,CAAC,CAAC;AAI9E,UAAU,0BAA0B,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IACzD,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;CACxB;AA2DD,eAAO,MAAM,kBAAkB;;MAQ1B,MAAM,YAAY,CAAC;AAExB,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,OAAO,gBAAgB,GACzD,0BAA0B,CAAC,CAAC,CAAC,CAAC"}
\ No newline at end of file
diff --git a/node_modules/react-native-reanimated/src/component/FlatList.tsx b/node_modules/react-native-reanimated/src/component/FlatList.tsx
index c886b0d..67b5ca5 100644
--- a/node_modules/react-native-reanimated/src/component/FlatList.tsx
+++ b/node_modules/react-native-reanimated/src/component/FlatList.tsx
@@ -16,7 +16,9 @@ import type { AnimatedProps } from '../helperTypes';

const AnimatedFlatList = createAnimatedComponent(FlatList);

-interface CellRendererComponentProps {
+interface CellRendererComponentProps<ItemT = any> {
+ index: number;
+ item: ItemT;
onLayout?: ((event: LayoutChangeEvent) => void) | undefined;
children: React.ReactNode;
style?: StyleProp<AnimatedStyle<ViewStyle>>;
@@ -25,6 +27,9 @@ interface CellRendererComponentProps {
const createCellRendererComponent = (
itemLayoutAnimationRef?: React.MutableRefObject<
ILayoutAnimationBuilder | undefined
+ >,
+ cellRendererComponentStyleRef?: React.MutableRefObject<
+ ReanimatedFlatListPropsWithLayout<any>['CellRendererComponentStyle']
>
) => {
const CellRendererComponent = (props: CellRendererComponentProps) => {
@@ -33,7 +38,15 @@ const createCellRendererComponent = (
// TODO TYPESCRIPT This is temporary cast is to get rid of .d.ts file.
layout={itemLayoutAnimationRef?.current as any}
onLayout={props.onLayout}
- style={props.style}>
+ style={[
+ props.style,
+ typeof cellRendererComponentStyleRef?.current === 'function'
+ ? cellRendererComponentStyleRef?.current({
+ index: props.index,
+ item: props.item,
+ })
+ : cellRendererComponentStyleRef?.current,
+ ]}>
{props.children}
</AnimatedView>
);
@@ -57,6 +70,20 @@ interface ReanimatedFlatListPropsWithLayout<T>
skipEnteringExitingAnimations?: boolean;
/** Property `CellRendererComponent` is not supported in `Animated.FlatList`. */
CellRendererComponent?: never;
+ /**
+ * Either animated view styles or a function that receives the item to be
+ * rendered and its index and returns animated view styles.
+ */
+ CellRendererComponentStyle:
+ | StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>
+ | (({
+ item,
+ index,
+ }: {
+ item: T;
+ index: number;
+ }) => StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>)
+ | undefined;
}

export type FlatListPropsWithLayout<T> = ReanimatedFlatListPropsWithLayout<T>;
@@ -73,8 +100,12 @@ const FlatListForwardRefRender = function <Item = any>(
props: ReanimatedFlatListPropsWithLayout<Item>,
ref: React.ForwardedRef<FlatList>
) {
- const { itemLayoutAnimation, skipEnteringExitingAnimations, ...restProps } =
- props;
+ const {
+ itemLayoutAnimation,
+ skipEnteringExitingAnimations,
+ CellRendererComponentStyle,
+ ...restProps
+ } = props;

// Set default scrollEventThrottle, because user expects
// to have continuous scroll events and
@@ -88,9 +119,16 @@ const FlatListForwardRefRender = function <Item = any>(
const itemLayoutAnimationRef = useRef(itemLayoutAnimation);
itemLayoutAnimationRef.current = itemLayoutAnimation;

+ const cellRendererComponentStyleRef = useRef(CellRendererComponentStyle);
+ cellRendererComponentStyleRef.current = CellRendererComponentStyle;
+
const CellRendererComponent = React.useMemo(
- () => createCellRendererComponent(itemLayoutAnimationRef),
- [itemLayoutAnimationRef]
+ () =>
+ createCellRendererComponent(
+ itemLayoutAnimationRef,
+ cellRendererComponentStyleRef
+ ),
+ [itemLayoutAnimationRef, CellRendererComponentStyle]
);

const animatedFlatList = (
67 changes: 43 additions & 24 deletions src/app/Scenes/HomeView/Sections/HomeViewSectionTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ContextModule } from "@artsy/cohesion"
import {
ArrowDownIcon,
ArrowUpIcon,
Box,
Flex,
FlexProps,
Skeleton,
Expand All @@ -27,10 +26,15 @@ import { NoFallback, withSuspense } from "app/utils/hooks/withSuspense"
import { ExtractNodeType } from "app/utils/relayHelpers"
import { AnimatePresence, MotiView } from "moti"
import { memo, RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { CellRendererProps, InteractionManager, ListRenderItem } from "react-native"
import { FlatList } from "react-native-gesture-handler"
import { InteractionManager, ListRenderItem, Platform } from "react-native"
import { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"
import { Easing } from "react-native-reanimated"
import Animated, {
Easing,
FadeOut,
LinearTransition,
useAnimatedStyle,
withTiming,
} from "react-native-reanimated"
import { graphql, useFragment, useLazyLoadQuery } from "react-relay"
import { usePrevious } from "react-use"

Expand All @@ -39,6 +43,8 @@ const MAX_NUMBER_OF_TASKS = 10
// Height of each task + seperator
const TASK_CARD_HEIGHT = 92

const IS_ANDROID = Platform.OS === "android"

type Task = ExtractNodeType<HomeViewSectionTasks_section$data["tasksConnection"]>

interface HomeViewSectionTasksProps extends FlexProps {
Expand Down Expand Up @@ -71,6 +77,10 @@ export const HomeViewSectionTasks: React.FC<HomeViewSectionTasksProps> = ({
const displayTaskStack = filteredTasks.length > 1 && !showAll
const HeaderIconComponent = showAll ? ArrowUpIcon : ArrowDownIcon

// TODO: remove this when this reanimated issue gets fixed
// https://github.com/software-mansion/react-native-reanimated/issues/5728
const [flatlistHeight, setFlatlistHeight] = useState<number | undefined>(undefined)

const task = tasks?.[0]

// adding the find-saved-artwork onboarding key to prevent overlap
Expand Down Expand Up @@ -122,8 +132,8 @@ export const HomeViewSectionTasks: React.FC<HomeViewSectionTasksProps> = ({
})
}

const renderCell = useCallback(({ index, ...rest }: CellRendererProps<Task>) => {
return <Box zIndex={-index} {...rest} />
const getCellZIndex = useCallback(({ index }: { index: number }) => {
return { zIndex: -index }
}, [])

const renderItem = useCallback<ListRenderItem<Task>>(
Expand Down Expand Up @@ -185,11 +195,19 @@ export const HomeViewSectionTasks: React.FC<HomeViewSectionTasksProps> = ({
</Flex>

<Flex mr={2}>
<FlatList
<Animated.FlatList
onLayout={(event) => {
if (IS_ANDROID) {
setFlatlistHeight(event.nativeEvent.layout.height)
}
}}
style={IS_ANDROID ? { height: flatlistHeight } : {}}
contentContainerStyle={IS_ANDROID ? { flex: 1, height: flatlistHeight } : {}}
itemLayoutAnimation={LinearTransition}
scrollEnabled={false}
data={filteredTasks}
keyExtractor={(item) => item.internalID}
CellRendererComponent={renderCell}
CellRendererComponentStyle={getCellZIndex}
ItemSeparatorComponent={() => <Spacer y={1} />}
renderItem={renderItem}
/>
Expand Down Expand Up @@ -268,23 +286,24 @@ const TaskItem = ({
}
}

const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ scaleX: withTiming(scaleX) }, { translateY: withTiming(translateY) }],
opacity: withTiming(opacity),
}
})

return (
<Flex>
<MotiView
key={task.internalID + index}
transition={{ type: "timing" }}
animate={{ transform: [{ scaleX }, { translateY }], opacity }}
>
<Task
disableSwipeable={displayTaskStack}
onClearTask={() => onClearTask(task)}
onPress={displayTaskStack ? () => setShowAll((prev) => !prev) : undefined}
ref={taskRef}
onOpenTask={onOpenTask}
task={task}
/>
</MotiView>
</Flex>
<Animated.View exiting={FadeOut} style={animatedStyle} key={task.internalID}>
<Task
disableSwipeable={displayTaskStack}
onClearTask={() => onClearTask(task)}
onPress={displayTaskStack ? () => setShowAll((prev) => !prev) : undefined}
ref={taskRef}
onOpenTask={onOpenTask}
task={task}
/>
</Animated.View>
)
}

Expand Down

0 comments on commit 9f6d733

Please sign in to comment.