Files
mattermost-mobile/app/hooks/gallery.ts
Daniel Espino García f4e6917185 Ensure no unresolved types in the definition files (#6521)
* Ensure no unresolved types in the definition files

* Address feedback and general cleanup

* Move import from @constants/x to @constants where relevant

* Remove unneeded "import as"
2022-08-05 14:36:19 +02:00

251 lines
7.1 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useCallback, useEffect, useRef} from 'react';
import {Platform} from 'react-native';
import {
Easing, makeRemote, runOnJS, useAnimatedRef, useAnimatedStyle, useEvent,
useSharedValue,
withTiming, WithTimingConfig,
} from 'react-native-reanimated';
import {useGallery} from '@context/gallery';
import type {Context, GestureHandlers, OnGestureEvent} from '@typings/screens/gallery';
import type {GestureHandlerGestureEvent} from 'react-native-gesture-handler';
function useRemoteContext<T extends object>(initialValue: T) {
const initRef = useRef<{ context: T } | null>(null);
if (initRef.current === null) {
initRef.current = {
context: makeRemote(initialValue ?? {}),
};
}
const {context} = initRef.current;
return context;
}
function diff(context: any, name: string, value: any) {
'worklet';
if (!context.___diffs) {
context.___diffs = {};
}
if (!context.___diffs[name]) {
context.___diffs[name] = {
stash: 0,
prev: null,
};
}
const d = context.___diffs[name];
d.stash = d.prev === null ? 0 : value - d.prev;
d.prev = value;
return d.stash;
}
export function useCreateAnimatedGestureHandler<T extends GestureHandlerGestureEvent, TContext extends Context>(handlers: GestureHandlers<T['nativeEvent'], TContext>) {
const context = useRemoteContext<any>({
__initialized: false,
});
const isAndroid = Platform.OS === 'android';
const handler = (event: T['nativeEvent']) => {
'worklet';
const FAILED = 1;
const BEGAN = 2;
const CANCELLED = 3;
const ACTIVE = 4;
const END = 5;
if (handlers.onInit && !context.__initialized) {
context.__initialized = true;
handlers.onInit(event, context);
}
if (handlers.onGesture) {
handlers.onGesture(event, context);
}
const stateDiff = diff(context, 'pinchState', event.state);
const pinchBeganAndroid = stateDiff === ACTIVE - BEGAN ? event.state === ACTIVE : false;
const isBegan = isAndroid ? pinchBeganAndroid : (event.state === BEGAN || event.oldState === BEGAN) &&
(event.velocityX !== 0 || event.velocityY !== 0);
if (isBegan) {
if (handlers.shouldHandleEvent) {
context._shouldSkip = !handlers.shouldHandleEvent(event, context);
} else {
context._shouldSkip = false;
}
} else if (typeof context._shouldSkip === 'undefined') {
return;
}
if (!context._shouldSkip && !context._shouldCancel) {
if (handlers.onEvent) {
handlers.onEvent(event, context);
}
if (handlers.shouldCancel) {
context._shouldCancel = handlers.shouldCancel(event, context);
if (context._shouldCancel) {
if (handlers.onEnd) {
handlers.onEnd(event, context, true);
}
return;
}
}
if (handlers.beforeEach) {
handlers.beforeEach(event, context);
}
if (isBegan && handlers.onStart) {
handlers.onStart(event, context);
}
if (event.state === ACTIVE && handlers.onActive) {
handlers.onActive(event, context);
}
if (event.oldState === ACTIVE && event.state === END && handlers.onEnd) {
handlers.onEnd(event, context, false);
}
if (event.oldState === ACTIVE && event.state === FAILED && handlers.onFail) {
handlers.onFail(event, context);
}
if (event.oldState === ACTIVE && event.state === CANCELLED && handlers.onCancel) {
handlers.onCancel(event, context);
}
if (event.oldState === ACTIVE) {
if (handlers.onFinish) {
handlers.onFinish(
event,
context,
event.state === CANCELLED || event.state === FAILED,
);
}
}
if (handlers.afterEach) {
handlers.afterEach(event, context);
}
}
// clean up context
if (event.oldState === ACTIVE) {
context._shouldSkip = undefined;
context._shouldCancel = undefined;
}
};
return handler;
}
export function useAnimatedGestureHandler<T extends GestureHandlerGestureEvent, TContext extends Context>(
handlers: GestureHandlers<T['nativeEvent'], TContext>,
): OnGestureEvent<T> {
const handler = useCallback(
useCreateAnimatedGestureHandler<T, TContext>(handlers),
[],
);
return useEvent<(event: T['nativeEvent']) => void, OnGestureEvent<T>>(
handler, ['onGestureHandlerStateChange', 'onGestureHandlerEvent'], false,
);
}
export function useGalleryControls() {
const controlsHidden = useSharedValue(false);
const translateYConfig: WithTimingConfig = {
duration: 400,
easing: Easing.bezier(0.33, 0.01, 0, 1),
};
const headerStyles = useAnimatedStyle(() => ({
opacity: controlsHidden.value ? withTiming(0) : withTiming(1),
transform: [
{
translateY: controlsHidden.value ? withTiming(-100, translateYConfig) : withTiming(0, translateYConfig),
},
],
position: 'absolute',
top: 0,
width: '100%',
zIndex: 1,
}));
const footerStyles = useAnimatedStyle(() => ({
opacity: controlsHidden.value ? withTiming(0) : withTiming(1),
transform: [
{
translateY: controlsHidden.value ? withTiming(100, translateYConfig) : withTiming(0, translateYConfig),
},
],
position: 'absolute',
bottom: 0,
width: '100%',
zIndex: 1,
}));
const setControlsHidden = useCallback((hidden: boolean) => {
'worklet';
if (controlsHidden.value === hidden) {
return;
}
controlsHidden.value = hidden;
}, []);
return {
controlsHidden,
headerStyles,
footerStyles,
setControlsHidden,
};
}
export function useGalleryItem(
identifier: string,
index: number,
onPress: (identifier: string, itemIndex: number) => void,
) {
const gallery = useGallery(identifier);
const ref = useAnimatedRef<any>();
const {opacity, activeIndex} = gallery!.sharedValues;
const styles = useAnimatedStyle(() => {
return {
opacity: activeIndex.value === index ? opacity.value : 1,
};
}, []);
useEffect(() => {
gallery.registerItem(index, ref);
}, []);
const onGestureEvent = () => {
'worklet';
activeIndex.value = index;
runOnJS(onPress)(identifier, index);
};
return {
ref,
styles,
onGestureEvent,
};
}