chore: Cleanup codebase (#137)
* Remove `useCachedState` * Add pressable opacity * Update Media.tsx * f * Update FormatFilter.ts * update * App -> CameraPage, Media -> MediaPage * Update CameraPage.tsx * Create 60 FPS switch * Update CameraPage.tsx
This commit is contained in:
parent
3bf4197b17
commit
f839bc23ac
@ -1,10 +1,9 @@
|
||||
import 'react-native-gesture-handler';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import { App } from './src/App';
|
||||
import { Settings } from './src/Settings';
|
||||
import { CameraPage } from './src/CameraPage';
|
||||
import { Splash } from './src/Splash';
|
||||
import { Media } from './src/Media';
|
||||
import { MediaPage } from './src/MediaPage';
|
||||
import { Camera } from 'react-native-vision-camera';
|
||||
|
||||
Navigation.setDefaultOptions({
|
||||
@ -42,19 +41,14 @@ Navigation.registerComponent(
|
||||
() => Splash,
|
||||
);
|
||||
Navigation.registerComponent(
|
||||
'Home',
|
||||
() => gestureHandlerRootHOC(App),
|
||||
() => App,
|
||||
'CameraPage',
|
||||
() => gestureHandlerRootHOC(CameraPage),
|
||||
() => CameraPage,
|
||||
);
|
||||
Navigation.registerComponent(
|
||||
'Media',
|
||||
() => gestureHandlerRootHOC(Media),
|
||||
() => Media,
|
||||
);
|
||||
Navigation.registerComponent(
|
||||
'Settings',
|
||||
() => gestureHandlerRootHOC(Settings),
|
||||
() => Settings,
|
||||
'MediaPage',
|
||||
() => gestureHandlerRootHOC(MediaPage),
|
||||
() => MediaPage,
|
||||
);
|
||||
|
||||
Navigation.events().registerNavigationButtonPressedListener((event) => {
|
||||
@ -67,7 +61,7 @@ Navigation.events().registerAppLaunchedListener(async () => {
|
||||
Camera.getMicrophonePermissionStatus(),
|
||||
]);
|
||||
let rootName = 'Splash';
|
||||
if (cameraPermission === 'authorized' && microphonePermission === 'authorized') rootName = 'Home';
|
||||
if (cameraPermission === 'authorized' && microphonePermission === 'authorized') rootName = 'CameraPage';
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
|
@ -14,11 +14,11 @@
|
||||
"@react-native-community/blur": "^3.6.0",
|
||||
"@react-native-community/cameraroll": "^4.0.2",
|
||||
"@react-native-community/slider": "^3.0.3",
|
||||
"pipestate": "^1.0.2",
|
||||
"react": "17.0.2",
|
||||
"react-native": "0.64",
|
||||
"react-native-gesture-handler": "^1.10.3",
|
||||
"react-native-navigation": "7.8.4-snapshot.1439",
|
||||
"react-native-pressable-opacity": "^1.0.4",
|
||||
"react-native-reanimated": "^2.1.0",
|
||||
"react-native-static-safe-area-insets": "^2.1.1",
|
||||
"react-native-vector-icons": "^8.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useState, useMemo, useCallback } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import {
|
||||
PinchGestureHandler,
|
||||
PinchGestureHandlerGestureEvent,
|
||||
@ -11,18 +11,16 @@ import {
|
||||
import { Navigation, NavigationFunctionComponent } from 'react-native-navigation';
|
||||
import type { CameraDeviceFormat, CameraRuntimeError, PhotoFile, VideoFile } from 'react-native-vision-camera';
|
||||
import { Camera, frameRateIncluded, sortFormatsByResolution, filterFormatsByAspectRatio } from 'react-native-vision-camera';
|
||||
import { useIsScreenFocused } from './hooks/useIsScreenFocused';
|
||||
import { useIsScreenFocussed } from './hooks/useIsScreenFocused';
|
||||
import { CONTENT_SPACING, MAX_ZOOM_FACTOR, SAFE_AREA_PADDING } from './Constants';
|
||||
import Reanimated, { Extrapolate, interpolate, useAnimatedGestureHandler, useAnimatedProps, useSharedValue } from 'react-native-reanimated';
|
||||
import { useEffect } from 'react';
|
||||
import { useIsForeground } from './hooks/useIsForeground';
|
||||
import { StatusBarBlurBackground } from './views/StatusBarBlurBackground';
|
||||
import { CaptureButton } from './views/CaptureButton';
|
||||
import { PressableOpacity } from './views/PressableOpacity';
|
||||
import { PressableOpacity } from 'react-native-pressable-opacity';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
import { useSelector } from 'pipestate';
|
||||
import { FpsSelector } from './state/selectors';
|
||||
import { useCameraDevice } from './hooks/useCameraDevice';
|
||||
|
||||
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
||||
@ -33,14 +31,14 @@ Reanimated.addWhitelistedNativeProps({
|
||||
const SCALE_FULL_ZOOM = 3;
|
||||
const BUTTON_SIZE = 40;
|
||||
|
||||
export const App: NavigationFunctionComponent = ({ componentId }) => {
|
||||
export const CameraPage: NavigationFunctionComponent = ({ componentId }) => {
|
||||
const camera = useRef<Camera>(null);
|
||||
const [isCameraInitialized, setIsCameraInitialized] = useState(false);
|
||||
const zoom = useSharedValue(0);
|
||||
const isPressingButton = useSharedValue(false);
|
||||
|
||||
// check if camera page is active
|
||||
const isFocussed = useIsScreenFocused(componentId);
|
||||
const isFocussed = useIsScreenFocussed(componentId);
|
||||
const isForeground = useIsForeground();
|
||||
const isActive = isFocussed && isForeground;
|
||||
|
||||
@ -59,32 +57,34 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
||||
}, [device?.formats]);
|
||||
|
||||
//#region Memos
|
||||
const [targetFps] = useSelector(FpsSelector);
|
||||
console.log(`Target FPS: ${targetFps}`);
|
||||
const [is60Fps, setIs60Fps] = useState(true);
|
||||
const fps = useMemo(() => {
|
||||
if (!is60Fps) return 30;
|
||||
|
||||
if (enableNightMode && !device?.supportsLowLightBoost) {
|
||||
// User has enabled Night Mode, but Night Mode is not natively supported, so we simulate it by lowering the frame rate.
|
||||
return 30;
|
||||
}
|
||||
|
||||
const supportsHdrAtHighFps = formats.some((f) => f.supportsVideoHDR && f.frameRateRanges.some((r) => frameRateIncluded(r, targetFps)));
|
||||
if (enableHdr && !supportsHdrAtHighFps) {
|
||||
// User has enabled HDR, but HDR is not supported at targetFps.
|
||||
const supportsHdrAt60Fps = formats.some((f) => f.supportsVideoHDR && f.frameRateRanges.some((r) => frameRateIncluded(r, 60)));
|
||||
if (enableHdr && !supportsHdrAt60Fps) {
|
||||
// User has enabled HDR, but HDR is not supported at 60 FPS.
|
||||
return 30;
|
||||
}
|
||||
|
||||
const supportsHighFps = formats.some((f) => f.frameRateRanges.some((r) => frameRateIncluded(r, targetFps)));
|
||||
if (!supportsHighFps) {
|
||||
// targetFps is not supported by any format.
|
||||
const supports60Fps = formats.some((f) => f.frameRateRanges.some((r) => frameRateIncluded(r, 60)));
|
||||
if (!supports60Fps) {
|
||||
// 60 FPS is not supported by any format.
|
||||
return 30;
|
||||
}
|
||||
// If nothing blocks us from using it, we default to targetFps.
|
||||
return targetFps;
|
||||
}, [device?.supportsLowLightBoost, enableHdr, enableNightMode, formats, targetFps]);
|
||||
// If nothing blocks us from using it, we default to 60 FPS.
|
||||
return 60;
|
||||
}, [device?.supportsLowLightBoost, enableHdr, enableNightMode, formats, is60Fps]);
|
||||
|
||||
const supportsCameraFlipping = useMemo(() => devices.back != null && devices.front != null, [devices.back, devices.front]);
|
||||
const supportsFlash = device?.hasFlash ?? false;
|
||||
const supportsHdr = useMemo(() => formats.some((f) => f.supportsVideoHDR), [formats]);
|
||||
const supports60Fps = useMemo(() => formats.some((f) => f.frameRateRanges.some((rate) => frameRateIncluded(rate, 60))), [formats]);
|
||||
const canToggleNightMode = enableNightMode
|
||||
? true // it's enabled so you have to be able to turn it off again
|
||||
: (device?.supportsLowLightBoost ?? false) || fps > 30; // either we have native support, or we can lower the FPS
|
||||
@ -136,7 +136,7 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
||||
console.log(`Media captured! ${JSON.stringify(media)}`);
|
||||
await Navigation.showModal({
|
||||
component: {
|
||||
name: 'Media',
|
||||
name: 'MediaPage',
|
||||
passProps: {
|
||||
type: type,
|
||||
path: media.path,
|
||||
@ -147,18 +147,9 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
||||
const onFlipCameraPressed = useCallback(() => {
|
||||
setCameraPosition((p) => (p === 'back' ? 'front' : 'back'));
|
||||
}, []);
|
||||
const onHdrSwitchPressed = useCallback(() => {
|
||||
setEnableHdr((h) => !h);
|
||||
}, []);
|
||||
const onFlashPressed = useCallback(() => {
|
||||
setFlash((f) => (f === 'off' ? 'on' : 'off'));
|
||||
}, []);
|
||||
const onNightModePressed = useCallback(() => {
|
||||
setEnableNightMode((n) => !n);
|
||||
}, []);
|
||||
const onSettingsPressed = useCallback(() => {
|
||||
Navigation.push(componentId, { component: { name: 'Settings' } });
|
||||
}, [componentId]);
|
||||
//#endregion
|
||||
|
||||
//#region Tap Gesture
|
||||
@ -269,19 +260,24 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
||||
<IonIcon name={flash === 'on' ? 'flash' : 'flash-off'} color="white" size={24} />
|
||||
</PressableOpacity>
|
||||
)}
|
||||
{canToggleNightMode && (
|
||||
<PressableOpacity style={styles.button} onPress={onNightModePressed} disabledOpacity={0.4}>
|
||||
<IonIcon name={enableNightMode ? 'moon' : 'moon-outline'} color="white" size={24} />
|
||||
{supports60Fps && (
|
||||
<PressableOpacity style={styles.button} onPress={() => setIs60Fps(!is60Fps)}>
|
||||
<Text style={styles.text}>
|
||||
{is60Fps ? '60' : '30'}
|
||||
{'\n'}FPS
|
||||
</Text>
|
||||
</PressableOpacity>
|
||||
)}
|
||||
{supportsHdr && (
|
||||
<PressableOpacity style={styles.button} onPress={onHdrSwitchPressed}>
|
||||
<PressableOpacity style={styles.button} onPress={() => setEnableHdr((h) => !h)}>
|
||||
<MaterialIcon name={enableHdr ? 'hdr' : 'hdr-off'} color="white" size={24} />
|
||||
</PressableOpacity>
|
||||
)}
|
||||
<PressableOpacity style={styles.button} onPress={onSettingsPressed}>
|
||||
<IonIcon name="settings-outline" color="white" size={24} />
|
||||
</PressableOpacity>
|
||||
{canToggleNightMode && (
|
||||
<PressableOpacity style={styles.button} onPress={() => setEnableNightMode(!enableNightMode)} disabledOpacity={0.4}>
|
||||
<IonIcon name={enableNightMode ? 'moon' : 'moon-outline'} color="white" size={24} />
|
||||
</PressableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@ -311,4 +307,10 @@ const styles = StyleSheet.create({
|
||||
right: SAFE_AREA_PADDING.paddingRight,
|
||||
top: SAFE_AREA_PADDING.paddingTop,
|
||||
},
|
||||
text: {
|
||||
color: 'white',
|
||||
fontSize: 11,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
@ -4,8 +4,8 @@ import { Navigation, NavigationFunctionComponent, OptionsModalPresentationStyle
|
||||
import Video, { LoadError, OnLoadData } from 'react-native-video';
|
||||
import { SAFE_AREA_PADDING } from './Constants';
|
||||
import { useIsForeground } from './hooks/useIsForeground';
|
||||
import { useIsScreenFocused } from './hooks/useIsScreenFocused';
|
||||
import { PressableOpacity } from './views/PressableOpacity';
|
||||
import { useIsScreenFocussed } from './hooks/useIsScreenFocused';
|
||||
import { PressableOpacity } from 'react-native-pressable-opacity';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
import { Alert } from 'react-native';
|
||||
import CameraRoll from '@react-native-community/cameraroll';
|
||||
@ -34,10 +34,10 @@ const requestSavePermission = async (): Promise<boolean> => {
|
||||
const isVideoOnLoadEvent = (event: OnLoadData | NativeSyntheticEvent<ImageLoadEventData>): event is OnLoadData =>
|
||||
'duration' in event && 'naturalSize' in event;
|
||||
|
||||
export const Media: NavigationFunctionComponent<MediaProps> = ({ componentId, type, path }) => {
|
||||
export const MediaPage: NavigationFunctionComponent<MediaProps> = ({ componentId, type, path }) => {
|
||||
const [hasMediaLoaded, setHasMediaLoaded] = useState(false);
|
||||
const isForeground = useIsForeground();
|
||||
const isScreenFocused = useIsScreenFocused(componentId);
|
||||
const isScreenFocused = useIsScreenFocussed(componentId);
|
||||
const isVideoPaused = !isForeground || !isScreenFocused;
|
||||
const [savingState, setSavingState] = useState<'none' | 'saving' | 'saved'>('none');
|
||||
|
||||
@ -126,7 +126,7 @@ export const Media: NavigationFunctionComponent<MediaProps> = ({ componentId, ty
|
||||
);
|
||||
};
|
||||
|
||||
Media.options = {
|
||||
MediaPage.options = {
|
||||
modal: {
|
||||
swipeToDismiss: false,
|
||||
},
|
@ -1,110 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { StyleSheet, View, Text, Linking } from 'react-native';
|
||||
import { Navigation, NavigationFunctionComponent } from 'react-native-navigation';
|
||||
import Slider from '@react-native-community/slider';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Camera } from 'react-native-vision-camera';
|
||||
import { SAFE_AREA_PADDING } from './Constants';
|
||||
import { useSelector } from 'pipestate';
|
||||
import { FpsSelector } from './state/selectors';
|
||||
|
||||
export const Settings: NavigationFunctionComponent = ({ componentId }) => {
|
||||
const [fpsSelector, setFpsSelector] = useSelector(FpsSelector);
|
||||
const [fps, setFps] = useState(fpsSelector);
|
||||
|
||||
const [minFps, setMinFps] = useState<number>();
|
||||
const [maxFps, setMaxFps] = useState<number>();
|
||||
|
||||
const onCuventPressed = useCallback(() => {
|
||||
Linking.openURL('https://cuvent.com');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadFormats = async (): Promise<void> => {
|
||||
const devices = await Camera.getAvailableCameraDevices();
|
||||
const formats = devices.flatMap((d) => d.formats);
|
||||
let max = 0,
|
||||
min = 0;
|
||||
formats.forEach((format) => {
|
||||
const frameRates = format.frameRateRanges.map((f) => f.maxFrameRate).sort((left, right) => left - right);
|
||||
const highest = frameRates[frameRates.length - 1] as number;
|
||||
const lowest = frameRates[0] as number;
|
||||
if (highest > max) max = highest;
|
||||
if (lowest < min) min = lowest;
|
||||
});
|
||||
setMaxFps(max);
|
||||
setMinFps(min);
|
||||
};
|
||||
loadFormats();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = Navigation.events().registerScreenPoppedListener((event) => {
|
||||
if (event.componentId === componentId) setFpsSelector(Math.round(fps));
|
||||
});
|
||||
return () => {
|
||||
listener.remove();
|
||||
};
|
||||
}, [componentId, fps, setFpsSelector]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.vControl}>
|
||||
<Text>Frame Rate (FPS): {Math.round(fps)}</Text>
|
||||
{minFps != null && maxFps != null && <Slider minimumValue={minFps} maximumValue={maxFps} value={fps} onValueChange={setFps} />}
|
||||
</View>
|
||||
|
||||
<View style={styles.spacer} />
|
||||
|
||||
<Text style={styles.aboutText}>
|
||||
Vision Camera is powered by{' '}
|
||||
<Text style={styles.hyperlink} onPress={onCuventPressed}>
|
||||
Cuvent
|
||||
</Text>
|
||||
.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
Settings.options = {
|
||||
topBar: {
|
||||
visible: true,
|
||||
title: {
|
||||
text: 'Settings',
|
||||
},
|
||||
backButton: {
|
||||
id: 'back',
|
||||
showTitle: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
...SAFE_AREA_PADDING,
|
||||
},
|
||||
aboutText: {
|
||||
alignSelf: 'center',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
color: '#A9A9A9',
|
||||
maxWidth: '50%',
|
||||
textAlign: 'center',
|
||||
},
|
||||
hyperlink: {
|
||||
color: '#007aff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
vControl: {
|
||||
width: '100%',
|
||||
},
|
||||
spacer: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
@ -1,26 +0,0 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Same as `useState`, but swallow all calls to `setState` if the value didn't change (uses `===` comparator per default)
|
||||
* @param initialValue The initial state
|
||||
* @param comparator A custom comparator, useful if you want to only round numbers or use string locale for comparison. Make sure this function is memoized!
|
||||
*/
|
||||
export const useCachedState = <T>(initialValue: T, comparator?: (oldState: T, newState: T) => boolean): [T, (newState: T) => void] => {
|
||||
const [state, setState] = useState(initialValue);
|
||||
const cachedState = useRef(initialValue);
|
||||
|
||||
const dispatchState = useCallback(
|
||||
(newState: T) => {
|
||||
const areEqual = comparator == null ? cachedState.current === newState : comparator(cachedState.current, newState);
|
||||
if (areEqual) {
|
||||
return;
|
||||
} else {
|
||||
cachedState.current = newState;
|
||||
setState(newState);
|
||||
}
|
||||
},
|
||||
[comparator],
|
||||
);
|
||||
|
||||
return [state, dispatchState];
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import { useCachedState } from './useCachedState';
|
||||
|
||||
export const useIsForeground = (): boolean => {
|
||||
const [isForeground, setIsForeground] = useCachedState(true);
|
||||
const [isForeground, setIsForeground] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (state: AppStateStatus): void => {
|
||||
|
@ -1,57 +1,39 @@
|
||||
import { useEffect, useMemo, useReducer } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
|
||||
type Action =
|
||||
| {
|
||||
action: 'push';
|
||||
componentId: string;
|
||||
}
|
||||
| {
|
||||
action: 'pop';
|
||||
componentId: string;
|
||||
};
|
||||
export const useIsScreenFocussed = (componentId: string): boolean => {
|
||||
const componentStack = useRef<string[]>(['componentId']);
|
||||
const [isFocussed, setIsFocussed] = useState(true);
|
||||
|
||||
const reducer = (stack: string[], action: Action): string[] => {
|
||||
switch (action.action) {
|
||||
case 'push': {
|
||||
stack.push(action.componentId);
|
||||
break;
|
||||
}
|
||||
case 'pop': {
|
||||
const index = stack.indexOf(action.componentId);
|
||||
if (index > -1) stack.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [...stack];
|
||||
};
|
||||
|
||||
export const useIsScreenFocused = (componentId: string): boolean => {
|
||||
const [componentStack, dispatch] = useReducer(reducer, [componentId]);
|
||||
const isFocussed = useMemo(() => componentStack[componentStack.length - 1] === componentId, [componentStack, componentId]);
|
||||
const invalidate = useCallback(() => {
|
||||
const last = componentStack.current[componentStack.current.length - 1];
|
||||
setIsFocussed(last === componentId);
|
||||
}, [componentId, setIsFocussed]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = Navigation.events().registerComponentDidAppearListener((event) => {
|
||||
if (event.componentType !== 'Component') return;
|
||||
dispatch({
|
||||
action: 'push',
|
||||
componentId: event.componentId,
|
||||
});
|
||||
componentStack.current.push(event.componentId);
|
||||
invalidate();
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, []);
|
||||
}, [invalidate]);
|
||||
useEffect(() => {
|
||||
const listener = Navigation.events().registerComponentDidDisappearListener((event) => {
|
||||
if (event.componentType !== 'Component') return;
|
||||
dispatch({
|
||||
action: 'pop',
|
||||
componentId: event.componentId,
|
||||
});
|
||||
// we can't simply use .pop() here because the component might be popped deeper down in the hierarchy.
|
||||
for (let i = componentStack.current.length - 1; i >= 0; i--) {
|
||||
if (componentStack.current[i] === event.componentId) {
|
||||
componentStack.current.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, []);
|
||||
}, [invalidate]);
|
||||
|
||||
return isFocussed;
|
||||
};
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { atom } from 'pipestate';
|
||||
|
||||
interface FormatSettings {
|
||||
fps: number;
|
||||
}
|
||||
|
||||
export const FormatSettingsAtom = atom<FormatSettings>({
|
||||
default: {
|
||||
fps: 60,
|
||||
},
|
||||
});
|
@ -1,13 +0,0 @@
|
||||
import { selector } from 'pipestate';
|
||||
import { FormatSettingsAtom } from './atoms';
|
||||
|
||||
export const FpsSelector = selector<number, []>({
|
||||
get: ({ get }) => {
|
||||
return get(FormatSettingsAtom).fps;
|
||||
},
|
||||
set: ({ set, get }, newFps) => {
|
||||
const formatSettings = get(FormatSettingsAtom);
|
||||
set(FormatSettingsAtom, { ...formatSettings, fps: newFps });
|
||||
},
|
||||
dependencies: [FormatSettingsAtom],
|
||||
});
|
@ -1,50 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { PressableProps, Pressable, PressableStateCallbackType, StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
export interface PressableOpacityProps extends PressableProps {
|
||||
/**
|
||||
* The opacity to use when `disabled={true}`
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
disabledOpacity?: number;
|
||||
/**
|
||||
* The opacity to animate to when the user presses the button
|
||||
*
|
||||
* @default 0.2
|
||||
*/
|
||||
activeOpacity?: number;
|
||||
}
|
||||
|
||||
export type StyleType = (state: PressableStateCallbackType) => StyleProp<ViewStyle>;
|
||||
|
||||
/**
|
||||
* A Pressable component that lowers opacity when in pressed state. Uses the JS Pressability API.
|
||||
*/
|
||||
export const PressableOpacity = ({
|
||||
style,
|
||||
disabled = false,
|
||||
disabledOpacity = 1,
|
||||
activeOpacity = 0.2,
|
||||
...passThroughProps
|
||||
}: PressableOpacityProps): React.ReactElement => {
|
||||
const getOpacity = useCallback(
|
||||
(pressed: boolean) => {
|
||||
if (disabled) {
|
||||
return disabledOpacity;
|
||||
} else {
|
||||
if (pressed) return activeOpacity;
|
||||
else return 1;
|
||||
}
|
||||
},
|
||||
[activeOpacity, disabled, disabledOpacity],
|
||||
);
|
||||
const _style = useCallback<StyleType>(({ pressed }) => [style as ViewStyle, { opacity: getOpacity(pressed) }], [getOpacity, style]);
|
||||
|
||||
return <Pressable style={_style} disabled={disabled} {...passThroughProps} />;
|
||||
};
|
||||
|
||||
// Fallback implementation using TouchableOpacity:
|
||||
// export default function PressableOpacity(props: TouchableOpacityProps & { children?: React.ReactNode }): React.ReactElement {
|
||||
// return <TouchableOpacity delayPressIn={0} {...props} />;
|
||||
// }
|
@ -4598,11 +4598,6 @@ pify@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
|
||||
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
||||
|
||||
pipestate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pipestate/-/pipestate-1.0.2.tgz#5b859ce947eb9180395199a44e7622b5700a0151"
|
||||
integrity sha512-1ZaIMyWESR2cb76AZfUosRBSWPQ/2s29naiV72N1iKixzLY1qdPUgZYV990L/pEt/9T/nZDQU7zXSy/aq1ttYw==
|
||||
|
||||
pirates@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
|
||||
@ -4783,6 +4778,11 @@ react-native-navigation@7.8.4-snapshot.1439:
|
||||
react-lifecycles-compat "2.0.0"
|
||||
tslib "1.9.3"
|
||||
|
||||
react-native-pressable-opacity@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-native-pressable-opacity/-/react-native-pressable-opacity-1.0.4.tgz#391f33fdc25cb84551f2743a25eced892b9f30f7"
|
||||
integrity sha512-DBIg7UoRiuBYiFEvx+XNMqH0OEx64WrSksXhT6Kq9XuyyKsThMNDqZ9G5QV7vfu7dU2/IctwIz5c0Xwkp4K3tA==
|
||||
|
||||
react-native-reanimated@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.1.0.tgz#b9ad04aee490e1e030d0a6cdaa43a14895d9a54d"
|
||||
|
@ -43,10 +43,12 @@ export type Size = {
|
||||
*/
|
||||
height: number;
|
||||
};
|
||||
|
||||
const SCREEN_SIZE: Size = {
|
||||
width: Dimensions.get('window').width,
|
||||
height: Dimensions.get('window').height,
|
||||
};
|
||||
|
||||
const applyScaledMask = (
|
||||
clippedElementDimensions: Size, // 12 x 12
|
||||
maskDimensions: Size, // 6 x 12
|
||||
@ -54,17 +56,11 @@ const applyScaledMask = (
|
||||
const wScale = maskDimensions.width / clippedElementDimensions.width; // 0.5
|
||||
const hScale = maskDimensions.height / clippedElementDimensions.height; // 1.0
|
||||
|
||||
if (wScale > hScale) {
|
||||
return {
|
||||
width: maskDimensions.width / hScale,
|
||||
height: maskDimensions.height / hScale,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
width: maskDimensions.width / wScale,
|
||||
height: maskDimensions.height / wScale,
|
||||
};
|
||||
}
|
||||
const scale = Math.min(wScale, hScale);
|
||||
return {
|
||||
width: maskDimensions.width / scale,
|
||||
height: maskDimensions.height / scale,
|
||||
};
|
||||
};
|
||||
|
||||
const getFormatAspectRatioOverflow = (format: CameraDeviceFormat, size: Size): number => {
|
||||
|
Loading…
Reference in New Issue
Block a user