forked from Ivasoft/mattermost-mobile
* 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"
251 lines
7.1 KiB
TypeScript
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,
|
|
};
|
|
}
|