Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions HACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ There are two hacks here:
- We hack the output of the compiler to provide clickable links for error messages. Relay assumes that you put your `__generated__` folder in the root of your project, but we put it in `src`.
- We make sure that files which do not change are not overwritten. This prevents excessive reloading by metro.

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

#### When can we remove this:

Expand All @@ -110,7 +110,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 @@ -135,7 +135,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 @@ -147,7 +147,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 @@ -161,7 +161,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 @@ -171,7 +171,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 @@ -183,7 +183,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 @@ -354,3 +354,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