Skip to content

Commit

Permalink
feat: new generate animation config (#157)
Browse files Browse the repository at this point in the history
* created new config

* Animation config composing

* Fixing crash

* Remove test style configs, export AnimationBuilder from lib

* Add AnimationBuilder class

* Add handling of stylesQueue from AnimationBuilder in useAnimationAPI

* Update todos

* Animation Builder is working

* fixed crash without new config

* add test for one animation

* fixed animations bug and tests

* Remaining animations, cleaning, tests

* Animation builder tests

* Docs update

* fix: fixed all eslint/typescript errors and warnings

* fix: deleted unneccessary fn export

* chore: deleted comment

Co-authored-by: mateki0 <mateki0@interia.pl>
Co-authored-by: Daniel Grychtoł <grychtol.daniel@gmail.com>
  • Loading branch information
3 people committed Dec 20, 2022
1 parent 6029a50 commit 095b844
Show file tree
Hide file tree
Showing 10 changed files with 560 additions and 35 deletions.
80 changes: 77 additions & 3 deletions docs/docs/intro/animations/custom-transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ When the notification is about to show up, the value is animated from `0` to `1`

Every time a notification is about to be shown, the library renders the UI part wrapped with an `<Animated.View />` and applies **animated styles** to it so it knows how it should animate.

The source of these styles comes from the **animation config** that is generated wih the `generateAnimationConfig` function and is used internally by the library to generate the animations. You can also use it to create whatever transition you desire.

To recap, there are *4 properties* that can control the transtion. They are all handled by `generateAnimationConfig` and go as follows:
The source of these styles comes from the **animation config** that is generated with `AnimationBuilder` class or `generateAnimationConfig` function and is used internally by the library to generate the animations. You can also use it yourself to create whatever transition you desiere.

Summarizing, there are _4 properties_ that can controll the transtion. They all are handled by `AnimationBuilder` or `generateAnimationConfig` and go as follows:

- `animationConfigIn` - spring / timing configuration for transition in. **REQUIRED**
- `animationConfigOut` - spring / timing configuration for transition out. **Not required**, fallbacks to `animationConfigIn` when not provided
Expand All @@ -34,13 +35,85 @@ To recap, there are *4 properties* that can control the transtion. They are all

The return type of this function (`generateAnimationConfig`) is `CustomAnimationConfig` which you can then use when changing the animation types in, e.g., `createNotification` or `notify` call.

### Generating transition config with `AnimationBuilder`

The `AnimationBuilder` takes in a config object as a property with which you can define the animation.

Below code snippets should give an idea how it works:

**Example 1**

```typescript
export const MoveDown = new AnimationBuilder({
animationConfigIn: {
type: 'timing',
config: {
duration: 400,
},
},
transitionInStyles: (progress) => {
'worklet'
const translateY = interpolate(progress.value, [0, 1], [-100, 0])
return {
opacity: progress.value,
transform: [{ translateY }],
}
},
transitionOutStyles: (progress) => {
'worklet'
const translateY = interpolate(progress.value, [0, 1], [100, 0])
return {
opacity: progress.value,
transform: [{ translateY }],
}
},
})
```

**Example 2**

```typescript
export const RotateIn = generateAnimationConfig({
animationConfigIn: {
type: 'timing',
config: {
duration: 700,
easing: Easing.out(Easing.exp),
},
},
transitionInStyles: (progress) => {
'worklet'

const rotate = interpolate(progress.value, [0, 1], [-360, 0])

return {
transform: [{ rotate: `${rotate}deg` }, { scale: progress.value }],
opacity: progress.value,
}
},
})
```

**Example 3**

```typescript
export const MoveDownRotateIn = MoveDown.add(RotateIn)
```

### Generating transition config with `generateAnimationConfig`

:::caution

generateAnimation config is `deprecated`. Please use Animation builder which allows your animations to be more customizable.

:::

The `generateAnimationConfig` takes in a config object as a property with which you can define the animation.

The code snippets below should give an idea how it works:

**Example 1**

```typescript
export const Example1 = generateAnimationConfig({
animationConfigIn: {
Expand All @@ -65,12 +138,13 @@ export const Example1 = generateAnimationConfig({
```

**Example 2**

```typescript
export const Example2 = generateAnimationConfig({
animationConfigIn: {
type: 'timing',
config: {
duration: 300
duration: 300,
},
},
animationConfigOut: {
Expand Down
5 changes: 4 additions & 1 deletion example-expo/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { NotificationsProvider } = createNotifications({

export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<GestureHandlerRootView style={styles.gestureHandlerWrapper}>
<NotificationsProvider />
<SafeAreaView style={styles.container}>
<DefaultExamples />
Expand All @@ -20,6 +20,9 @@ export default function App() {
}

const styles = StyleSheet.create({
gestureHandlerWrapper: {
flex: 1,
},
container: {
flex: 1,
backgroundColor: '#fff',
Expand Down
13 changes: 1 addition & 12 deletions example/src/screens/AnimationsExamples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react'
import { SafeAreaView } from 'react-native'
import {
createNotifications,
RotateInRotateOut,
RotateZIn,
SlideInLeftSlideOutRight,
ZoomInDownZoomOutDown,
Expand Down Expand Up @@ -99,17 +98,7 @@ export const AnimationsExamples = () => {
}
buttonText="Zoom In Bounce"
/>
<AnimationButton
onPress={() =>
notify('success', {
params: {
...baseNotifyConfig,
},
config: { animationConfig: RotateInRotateOut },
})
}
buttonText="Rotate In"
/>

<AnimationButton
onPress={() =>
notify('success', {
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
],
"scripts": {
"test": "jest",
"test:watch": "jest --watch --detectOpenHandles",
"typescript": "tsc --noEmit",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"prepare": "bob build",
Expand Down Expand Up @@ -83,6 +84,12 @@
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
],
"testPathIgnorePatterns": [
"/node_modules/"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?@?react-native|react-native|@react-navigation|@react-native-community)"
]
},
"commitlint": {
Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/animationBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { mergeStylesFunctions } from 'src/core/hooks/useAnimationAPI'
import {
AnimationBuilder,
FadeIn,
MoveDownAnimation,
MoveUpAnimation,
RotateZIn,
SlideInLeftAnimation,
CrazyAnimationConfig,
} from 'src/core/utils/generateAnimationConfig'

describe('props merger tests', function () {
it('should make an instance from class', () => {
const MoveDown = new AnimationBuilder(MoveDownAnimation)

expect(MoveDown).toMatchObject(MoveDownAnimation)
})

it('should merge styles properly with add method', () => {
const MoveDown = new AnimationBuilder(MoveDownAnimation)
const MoveDownFadeIn = FadeIn.add(MoveDown).transitionInStylesQueue
const styles = mergeStylesFunctions(MoveDownFadeIn, { value: 1 })

expect(styles).toMatchObject({
opacity: 1,
transform: [{ translateY: 0 }, { translateX: 0 }, { translateX: 0 }, { translateY: 0 }],
})
})
it('should merge animationConfigIn properly with add method outstyles', () => {
const SlideInLeft = new AnimationBuilder(SlideInLeftAnimation)
const SlideInLeftCrazy = SlideInLeft.add(CrazyAnimationConfig).animationConfigIn

const configIn = CrazyAnimationConfig.animationConfigIn

expect(SlideInLeftCrazy).toMatchObject(configIn)
})
it('should merge styles properly with add method outstyles', () => {
const MoveUp = new AnimationBuilder(MoveUpAnimation)
const MoveUpRotateZIn = RotateZIn.add(MoveUp).transitionOutStylesQueue
const styles = mergeStylesFunctions(MoveUpRotateZIn, { value: 1 })

expect(styles).toMatchObject({
opacity: 1,
transform: [{ translateY: 0 }, { translateX: 0 }, { translateY: 0 }],
})
})
})
50 changes: 46 additions & 4 deletions src/core/hooks/useAnimationAPI.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
import { useCallback } from 'react'
import type { ViewStyle, TextStyle, ImageStyle } from 'react-native'
import {
cancelAnimation,
SharedValue,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
AnimateStyle,
} from 'react-native-reanimated'
import { useDrag } from './useDrag'
import type { NotificationState } from './useNotificationsStates'
import { emitter } from '../services/NotificationEmitter'
import { withAnimationCallbackJSThread } from '../utils/animation'
import { AnimationRange } from '../../types/animations'
import { AnimationRange, TransitionStylesConfigFunction } from '../../types/animations'
import { useTimer } from './useTimer'
import type { AnimationBuilder } from '../utils/generateAnimationConfig'

type Styles = AnimateStyle<ViewStyle | TextStyle | ImageStyle>

const mergeStylesObjects = (styles: Styles, newStyles: Styles) => {
'worklet'

const oldTransform = [...(styles?.transform || [])]
const newTransform = [...(newStyles?.transform || [])]

return {
...styles,
...newStyles,
transform: [...oldTransform, ...newTransform],
}
}

export const mergeStylesFunctions = (
stylesFunctions: TransitionStylesConfigFunction[],
progress: SharedValue<number>
) => {
'worklet'

return stylesFunctions.reduce<Styles>(
(accumulatedStyles, styleFunction) => {
return mergeStylesObjects(accumulatedStyles, styleFunction(progress) as Styles)
},
{ opacity: 1, transform: [{ translateY: 0 }, { translateX: 0 }] } // it has to have the default opacity value
)
}

export const useAnimationAPI = ({
gestureConfig,
Expand Down Expand Up @@ -98,13 +131,22 @@ export const useAnimationAPI = ({
const handleDragStateChange = dragStateHandler(dismiss, resetDrag)

const animatedStyles = useAnimatedStyle(() => {
const animationBuilder: AnimationBuilder = animationConfig as AnimationBuilder
const { transitionInStyles, transitionOutStyles } = animationConfig

if (
['out', 'idle_active'].includes(currentTransitionType.value) &&
animationBuilder.transitionOutStylesQueue?.length > 0
) {
return mergeStylesFunctions(animationBuilder.transitionOutStylesQueue, progress)
}
if (animationBuilder?.transitionInStylesQueue?.length > 0) {
return mergeStylesFunctions(animationBuilder.transitionInStylesQueue, progress)
}
if (['out', 'idle_active'].includes(currentTransitionType.value) && transitionOutStyles) {
return transitionOutStyles(progress)
return { opacity: 1, ...(transitionOutStyles(progress) as unknown as {}) }
}

return transitionInStyles(progress)
return { opacity: 1, ...(transitionInStyles(progress) as unknown as {}) }
})

return {
Expand Down
Loading

0 comments on commit 095b844

Please sign in to comment.