Skip to content

Commit

Permalink
fix: bring back reanimated and gesture handler (#1019)
Browse files Browse the repository at this point in the history
Co-authored-by: Vishal Narkhede <[email protected]>
  • Loading branch information
santhoshvai and vishalnarkhede authored Aug 31, 2023
1 parent 13c6da4 commit f90d140
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 11 deletions.
5 changes: 5 additions & 0 deletions packages/react-native-sdk/jest-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ jest.mock('@notifee/react-native', () =>
require('@notifee/react-native/jest-mock'),
);

jest.mock('react-native-reanimated', () => {
const RNReanimatedmock = require('react-native-reanimated/mock');
return { ...RNReanimatedmock, runOnUI: (fn: any) => fn };
});

// When mocking we implement only the needed navigator APIs, hence the suppression rule
// @ts-ignore
global.navigator = {
Expand Down
5 changes: 4 additions & 1 deletion packages/react-native-sdk/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { Config } from 'jest';

const config: Config = {
preset: 'react-native',
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
setupFilesAfterEnv: [
'<rootDir>/jest-setup.ts',
'<rootDir>/node_modules/react-native-gesture-handler/jestSetup.js',
],
testPathIgnorePatterns: [
'<rootDir>/__tests__/mocks/',
'<rootDir>/__tests__/utils/',
Expand Down
23 changes: 21 additions & 2 deletions packages/react-native-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,25 @@
"files": [
"dist",
"src",
"ios",
"android",
"ios",
"cpp",
"index.ts",
"stream-video-react-native.podspec",
"package.json",
"README.md",
"LICENSE",
"CHANGELOG.md"
"CHANGELOG.md",
"!ios/build",
"!android/build",
"!android/gradle",
"!android/gradlew",
"!android/gradlew.bat",
"!android/local.properties",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__",
"!**/.*"
],
"dependencies": {
"@stream-io/i18n": "workspace:^",
Expand Down Expand Up @@ -63,6 +74,12 @@
"react-native-callkeep": {
"optional": true
},
"react-native-gesture-handler": {
"optional": true
},
"react-native-reanimated": {
"optional": true
},
"react-native-voip-push-notification": {
"optional": true
}
Expand All @@ -88,7 +105,9 @@
"react-native": "0.71.8",
"react-native-callkeep": "4.3.11",
"react-native-device-info": "^10.6.0",
"react-native-gesture-handler": "2.8.0",
"react-native-incall-manager": "^4.0.0",
"react-native-reanimated": "2.7.0",
"react-native-svg": "^13.6.0",
"react-native-voip-push-notification": "3.3.1",
"react-test-renderer": "^18.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import React, { useMemo, useEffect, useRef, MutableRefObject } from 'react';
import { LayoutRectangle, View } from 'react-native';
import {
FloatingViewAlignment,
getSnapAlignments,
getClosestSnapAlignment,
floatingChildViewContainerStyle,
FloatingViewProps,
} from './common';
type GestureHandlerExportsType = typeof import('react-native-gesture-handler');
type ReanimatedNamespaceType = typeof import('react-native-reanimated').default;
type ReanimatedExportsType = typeof import('react-native-reanimated');

let ReanimatedFloatingView: React.FC<FloatingViewProps> = () => {
throw new Error(
'ReanimatedFloatingView component must not be used without the react-native-reanimated library and react-native-gesture-handler library installed',
);
};

try {
const {
Gesture,
GestureDetector,
}: {
Gesture: GestureHandlerExportsType['Gesture'];
GestureDetector: GestureHandlerExportsType['GestureDetector'];
} = require('react-native-gesture-handler');
const Reanimated: ReanimatedNamespaceType =
require('react-native-reanimated').default;
const {
useSharedValue,
withTiming,
useAnimatedStyle,
withDelay,
}: {
useSharedValue: ReanimatedExportsType['useSharedValue'];
withTiming: ReanimatedExportsType['withTiming'];
useAnimatedStyle: ReanimatedExportsType['useAnimatedStyle'];
withDelay: ReanimatedExportsType['withDelay'];
} = require('react-native-reanimated');

ReanimatedFloatingView = ({
initialAlignment,
containerHeight,
containerWidth,
children,
}: FloatingViewProps) => {
// to store the starting position of the gesture
const startRef: MutableRefObject<{ x: number; y: number } | null> = useRef<{
x: number;
y: number;
}>({
x: 0,
y: 0,
});
// to store the necessary translate x, y position
const translationX = useSharedValue(0);
const translationY = useSharedValue(0);
// we don't want to show the floating view until we have the layout rectangle
const opacity = useSharedValue(0);
const [rectangle, setRectangle] = React.useState<LayoutRectangle>();

const snapAlignments = useMemo(() => {
if (!rectangle) {
return {
[FloatingViewAlignment.topLeft]: { x: 0, y: 0 },
[FloatingViewAlignment.topRight]: { x: 0, y: 0 },
[FloatingViewAlignment.bottomLeft]: { x: 0, y: 0 },
[FloatingViewAlignment.bottomRight]: { x: 0, y: 0 },
};
}

return getSnapAlignments({
rootContainerDimensions: {
width: containerWidth,
height: containerHeight,
},
floatingViewDimensions: {
width: rectangle.width,
height: rectangle.height,
},
});
}, [rectangle, containerWidth, containerHeight]);

const dragGesture = useMemo(
() =>
Gesture.Pan()
.onStart((_e) => {
// store the starting position of the gesture
startRef.current = {
x: translationX.value,
y: translationY.value,
};
})
.onUpdate((e) => {
// update the translation with the distance of the gesture + starting position
translationX.value = Math.max(
0,
Math.min(
e.translationX + (startRef.current?.x ?? 0),
snapAlignments[FloatingViewAlignment.bottomRight].x,
),
);
translationY.value = Math.max(
0,
Math.min(
e.translationY + (startRef.current?.y ?? 0),
snapAlignments[FloatingViewAlignment.bottomRight].y,
),
);
})
.onEnd(() => {
// snap to the closest alignment with a spring animation
const position = {
x: translationX.value,
y: translationY.value,
};
const closestAlignment = getClosestSnapAlignment({
position,
snapAlignments,
});
translationX.value = withTiming(closestAlignment.x);
translationY.value = withTiming(closestAlignment.y);
}),
[snapAlignments, translationX, translationY],
);

useEffect(() => {
if (!rectangle) {
return;
}
const alignment = snapAlignments[initialAlignment];
startRef.current = alignment;

translationX.value = alignment.x;
translationY.value = alignment.y;

// add a small delay to the opacity animation to avoid
// the floating view to be visible when it is being moved
opacity.value = withDelay(500, withTiming(1, { duration: 50 }));
}, [
rectangle,
snapAlignments,
initialAlignment,
opacity,
translationX,
translationY,
]);

const animatedStyle = useAnimatedStyle(() => {
return {
height: rectangle?.height,
width: rectangle?.width,
opacity: opacity.value,
// to keep the value in the bounds we use min and max
transform: [
{
translateX: translationX.value,
},
{
translateY: translationY.value,
},
],
};
});

return (
// gesture handler root view must absolutely fill the bounds
// to intercept gestures within those bounds
<GestureDetector gesture={dragGesture}>
<Reanimated.View style={animatedStyle}>
<View
onLayout={(event) => {
const layout = event.nativeEvent.layout;
setRectangle((prev) => {
if (
prev &&
prev.width === layout.width &&
prev.height === layout.height &&
prev.x === layout.x &&
prev.y === layout.y
) {
return prev;
}
return layout;
});
}}
style={floatingChildViewContainerStyle}
>
{children}
</View>
</Reanimated.View>
</GestureDetector>
);
};
} catch (e) {}

export default ReanimatedFloatingView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {
getGestureHandlerLib,
getReanimatedLib,
} from '../../../../utils/internal/optionalLibs';
import { FloatingViewProps } from './common';

const FloatingView: React.FC<FloatingViewProps> =
getReanimatedLib() && getGestureHandlerLib()
? require('./ReanimatedFloatingView').default
: require('./AnimatedFloatingView').default;

export default FloatingView;
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
import { FLOATING_VIDEO_VIEW_STYLE, Z_INDEX } from '../../../constants';
import { ComponentTestIds } from '../../../constants/TestIds';
import { VideoSlash } from '../../../icons';
import FloatingView from './FloatingView';
import { CallParticipantsListProps } from '../../Call';
import { FloatingViewAlignment } from './FloatingView/common';
import {
ParticipantView as DefaultParticipantView,
ParticipantViewComponentProps,
} from '../ParticipantView';
import { useTheme } from '../../../contexts/ThemeContext';
import AnimatedFloatingView from './FloatingView/AnimatedFloatingView';
import { StreamVideoParticipant } from '@stream-io/video-client';

export type FloatingParticipantViewAlignment =
Expand Down Expand Up @@ -138,7 +138,7 @@ export const FloatingParticipantView = ({
}}
>
{containerDimensions && (
<AnimatedFloatingView
<FloatingView
containerHeight={containerDimensions.height}
containerWidth={containerDimensions.width}
initialAlignment={floatingAlignmentMap[alignment]}
Expand All @@ -164,7 +164,7 @@ export const FloatingParticipantView = ({
/>
)}
</Pressable>
</AnimatedFloatingView>
</FloatingView>
)}
</View>
);
Expand Down
27 changes: 27 additions & 0 deletions packages/react-native-sdk/src/utils/internal/optionalLibs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type GestureHandlerType = typeof import('react-native-gesture-handler');
export type ReanimatedType = typeof import('react-native-reanimated');

let gestureHandler: GestureHandlerType | undefined;
let reanimated: ReanimatedType | undefined;

try {
gestureHandler = require('react-native-gesture-handler');
} catch (e) {}

try {
reanimated = require('react-native-reanimated');
} catch (e) {}

export const getReanimatedLib = (onPackageNotFound = () => {}) => {
if (!reanimated) {
onPackageNotFound();
}
return reanimated;
};

export const getGestureHandlerLib = (onPackageNotFound = () => {}) => {
if (!gestureHandler) {
onPackageNotFound();
}
return gestureHandler;
};
14 changes: 10 additions & 4 deletions packages/react-native-sdk/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"compilerOptions": {
"outDir": "./dist",
"target": "esnext",
"module": "commonjs",
"types": ["react-native", "jest"],
"lib": ["es2019"],
"declaration": true,
"lib": ["esnext"],
"module": "esnext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
Expand All @@ -15,8 +15,14 @@
"strict": true,
"strictNullChecks": true,
"sourceMap": true,
"jsx": "react-native"
"jsx": "react"
},
"include": ["./src", "index.ts", "__tests__", "./jest-setup.ts", "version.ts"],
"include": [
"./src",
"index.ts",
"__tests__",
"./jest-setup.ts",
"version.ts"
],
"exclude": ["./dist", "node_modules"]
}
Loading

0 comments on commit f90d140

Please sign in to comment.