chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,7 @@
import * as React from 'react';
// @ts-ignore file to be used only if `react-native-reanimated` available in the project
import Animated from 'react-native-reanimated';
export default React.createContext<Animated.SharedValue<number> | undefined>(
undefined,
);
@@ -0,0 +1,118 @@
import React from 'react';
import { Platform } from 'react-native';
import { InnerScreen } from '../components/Screen';
import {
HeaderHeightChangeEventType,
ScreenProps,
TransitionProgressEventType,
} from '../types';
// @ts-ignore file to be used only if `react-native-reanimated` available in the project
import Animated, { useEvent, useSharedValue } from 'react-native-reanimated';
import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext';
import {
useSafeAreaFrame,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import getDefaultHeaderHeight from '../native-stack/utils/getDefaultHeaderHeight';
import getStatusBarHeight from '../native-stack/utils/getStatusBarHeight';
import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext';
const AnimatedScreen = Animated.createAnimatedComponent(
InnerScreen as unknown as React.ComponentClass,
);
// We use prop added to global by reanimated since it seems safer than the one from RN. See:
// https://github.com/software-mansion/react-native-reanimated/blob/3fe8b35b05e82b2f2aefda1fb97799cf81e4b7bb/src/reanimated2/UpdateProps.ts#L46
// @ts-expect-error nativeFabricUIManager is not yet included in the RN types
const ENABLE_FABRIC = !!global?.RN$Bridgeless;
const ReanimatedNativeStackScreen = React.forwardRef<
typeof AnimatedScreen,
ScreenProps
>((props, ref) => {
const { children, ...rest } = props;
const { stackPresentation = 'push', hasLargeHeader } = rest;
const dimensions = useSafeAreaFrame();
const topInset = useSafeAreaInsets().top;
const isStatusBarTranslucent = rest.statusBarTranslucent ?? false;
const statusBarHeight = getStatusBarHeight(
topInset,
dimensions,
isStatusBarTranslucent,
);
// Default header height, normally used in `useHeaderHeight` hook.
// Here, it is used for returning a default value for shared value.
const defaultHeaderHeight = getDefaultHeaderHeight(
dimensions,
statusBarHeight,
stackPresentation,
hasLargeHeader,
);
const cachedHeaderHeight = React.useRef(defaultHeaderHeight);
const headerHeight = useSharedValue(defaultHeaderHeight);
const progress = useSharedValue(0);
const closing = useSharedValue(0);
const goingForward = useSharedValue(0);
return (
<AnimatedScreen
// @ts-ignore some problems with ref and onTransitionProgressReanimated being "fake" prop for parsing of `useEvent` return value
ref={ref}
onTransitionProgressReanimated={useEvent(
(event: TransitionProgressEventType) => {
'worklet';
progress.value = event.progress;
closing.value = event.closing;
goingForward.value = event.goingForward;
},
[
// This should not be necessary, but is not properly managed by `react-native-reanimated`
// @ts-ignore wrong type
Platform.OS === 'android'
? 'onTransitionProgress'
: // for some reason there is a difference in required event name between architectures
ENABLE_FABRIC
? 'onTransitionProgress'
: 'topTransitionProgress',
],
)}
onHeaderHeightChangeReanimated={useEvent(
(event: HeaderHeightChangeEventType) => {
'worklet';
if (event.headerHeight !== cachedHeaderHeight.current) {
headerHeight.value = event.headerHeight;
cachedHeaderHeight.current = event.headerHeight;
}
},
[
// @ts-ignore wrong type
Platform.OS === 'android'
? 'onHeaderHeightChange'
: ENABLE_FABRIC
? 'onHeaderHeightChange'
: 'topHeaderHeightChange',
],
)}
{...rest}>
<ReanimatedHeaderHeightContext.Provider value={headerHeight}>
<ReanimatedTransitionProgressContext.Provider
value={{
progress,
closing,
goingForward,
}}>
{children}
</ReanimatedTransitionProgressContext.Provider>
</ReanimatedHeaderHeightContext.Provider>
</AnimatedScreen>
);
});
ReanimatedNativeStackScreen.displayName = 'ReanimatedNativeStackScreen';
export default ReanimatedNativeStackScreen;
@@ -0,0 +1,26 @@
import React from 'react';
import { InnerScreen } from '../components/Screen';
import { ScreenProps } from '../types';
// @ts-ignore file to be used only if `react-native-reanimated` available in the project
import Animated from 'react-native-reanimated';
const AnimatedScreen = Animated.createAnimatedComponent(
InnerScreen as unknown as React.ComponentClass,
);
const ReanimatedScreen = React.forwardRef<typeof AnimatedScreen, ScreenProps>(
(props, ref) => {
return (
<AnimatedScreen
// @ts-ignore some problems with ref and onTransitionProgressReanimated being "fake" prop for parsing of `useEvent` return value
ref={ref}
{...props}
/>
);
},
);
ReanimatedScreen.displayName = 'ReanimatedScreen';
export default ReanimatedScreen;
@@ -0,0 +1,43 @@
import React, { PropsWithChildren } from 'react';
import { View } from 'react-native';
import { ScreenContext } from '../components/Screen';
import { ScreenProps } from '../types';
import ReanimatedNativeStackScreen from './ReanimatedNativeStackScreen';
import AnimatedScreen from './ReanimatedScreen';
class ReanimatedScreenWrapper extends React.Component<ScreenProps> {
private ref: React.ElementRef<typeof View> | null = null;
setNativeProps(props: ScreenProps): void {
this.ref?.setNativeProps(props);
}
setRef = (ref: React.ElementRef<typeof View> | null): void => {
this.ref = ref;
this.props.onComponentRef?.(ref);
};
render() {
const ReanimatedScreen = this.props.isNativeStack
? ReanimatedNativeStackScreen
: AnimatedScreen;
return (
<ReanimatedScreen
{...this.props}
// @ts-ignore some problems with ref
ref={this.setRef}
/>
);
}
}
export default function ReanimatedScreenProvider(
props: PropsWithChildren<unknown>,
) {
return (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<ScreenContext.Provider value={ReanimatedScreenWrapper as any}>
{props.children}
</ScreenContext.Provider>
);
}
@@ -0,0 +1,13 @@
import * as React from 'react';
// @ts-ignore file to be used only if `react-native-reanimated` available in the project
import Animated from 'react-native-reanimated';
type ReanimatedTransitionProgressContextBody = {
progress: Animated.SharedValue<number>;
closing: Animated.SharedValue<number>;
goingForward: Animated.SharedValue<number>;
};
export default React.createContext<
ReanimatedTransitionProgressContextBody | undefined
>(undefined);
@@ -0,0 +1,3 @@
export { default as ReanimatedScreenProvider } from './ReanimatedScreenProvider';
export { default as useReanimatedTransitionProgress } from './useReanimatedTransitionProgress';
export { default as useReanimatedHeaderHeight } from './useReanimatedHeaderHeight';
@@ -0,0 +1,14 @@
import * as React from 'react';
import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext';
export default function useReanimatedHeaderHeight() {
const height = React.useContext(ReanimatedHeaderHeightContext);
if (height === undefined) {
throw new Error(
"Couldn't find the header height using Reanimated. Are you inside a screen in a navigator with a header and your NavigationContainer is wrapped in ReanimatedScreenProvider?",
);
}
return height;
}
@@ -0,0 +1,14 @@
import * as React from 'react';
import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext';
export default function useReanimatedTransitionProgress() {
const progress = React.useContext(ReanimatedTransitionProgressContext);
if (progress === undefined) {
throw new Error(
"Couldn't find values for reanimated transition progress. Are you inside a screen in Native Stack?",
);
}
return progress;
}