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 'react-native-gesture-handler';
|
||||||
import { Navigation } from 'react-native-navigation';
|
import { Navigation } from 'react-native-navigation';
|
||||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||||
import { App } from './src/App';
|
import { CameraPage } from './src/CameraPage';
|
||||||
import { Settings } from './src/Settings';
|
|
||||||
import { Splash } from './src/Splash';
|
import { Splash } from './src/Splash';
|
||||||
import { Media } from './src/Media';
|
import { MediaPage } from './src/MediaPage';
|
||||||
import { Camera } from 'react-native-vision-camera';
|
import { Camera } from 'react-native-vision-camera';
|
||||||
|
|
||||||
Navigation.setDefaultOptions({
|
Navigation.setDefaultOptions({
|
||||||
@ -42,19 +41,14 @@ Navigation.registerComponent(
|
|||||||
() => Splash,
|
() => Splash,
|
||||||
);
|
);
|
||||||
Navigation.registerComponent(
|
Navigation.registerComponent(
|
||||||
'Home',
|
'CameraPage',
|
||||||
() => gestureHandlerRootHOC(App),
|
() => gestureHandlerRootHOC(CameraPage),
|
||||||
() => App,
|
() => CameraPage,
|
||||||
);
|
);
|
||||||
Navigation.registerComponent(
|
Navigation.registerComponent(
|
||||||
'Media',
|
'MediaPage',
|
||||||
() => gestureHandlerRootHOC(Media),
|
() => gestureHandlerRootHOC(MediaPage),
|
||||||
() => Media,
|
() => MediaPage,
|
||||||
);
|
|
||||||
Navigation.registerComponent(
|
|
||||||
'Settings',
|
|
||||||
() => gestureHandlerRootHOC(Settings),
|
|
||||||
() => Settings,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Navigation.events().registerNavigationButtonPressedListener((event) => {
|
Navigation.events().registerNavigationButtonPressedListener((event) => {
|
||||||
@ -67,7 +61,7 @@ Navigation.events().registerAppLaunchedListener(async () => {
|
|||||||
Camera.getMicrophonePermissionStatus(),
|
Camera.getMicrophonePermissionStatus(),
|
||||||
]);
|
]);
|
||||||
let rootName = 'Splash';
|
let rootName = 'Splash';
|
||||||
if (cameraPermission === 'authorized' && microphonePermission === 'authorized') rootName = 'Home';
|
if (cameraPermission === 'authorized' && microphonePermission === 'authorized') rootName = 'CameraPage';
|
||||||
|
|
||||||
Navigation.setRoot({
|
Navigation.setRoot({
|
||||||
root: {
|
root: {
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
"@react-native-community/blur": "^3.6.0",
|
"@react-native-community/blur": "^3.6.0",
|
||||||
"@react-native-community/cameraroll": "^4.0.2",
|
"@react-native-community/cameraroll": "^4.0.2",
|
||||||
"@react-native-community/slider": "^3.0.3",
|
"@react-native-community/slider": "^3.0.3",
|
||||||
"pipestate": "^1.0.2",
|
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-native": "0.64",
|
"react-native": "0.64",
|
||||||
"react-native-gesture-handler": "^1.10.3",
|
"react-native-gesture-handler": "^1.10.3",
|
||||||
"react-native-navigation": "7.8.4-snapshot.1439",
|
"react-native-navigation": "7.8.4-snapshot.1439",
|
||||||
|
"react-native-pressable-opacity": "^1.0.4",
|
||||||
"react-native-reanimated": "^2.1.0",
|
"react-native-reanimated": "^2.1.0",
|
||||||
"react-native-static-safe-area-insets": "^2.1.1",
|
"react-native-static-safe-area-insets": "^2.1.1",
|
||||||
"react-native-vector-icons": "^8.0.0",
|
"react-native-vector-icons": "^8.0.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRef, useState, useMemo, useCallback } from 'react';
|
import { useRef, useState, useMemo, useCallback } from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import {
|
import {
|
||||||
PinchGestureHandler,
|
PinchGestureHandler,
|
||||||
PinchGestureHandlerGestureEvent,
|
PinchGestureHandlerGestureEvent,
|
||||||
@ -11,18 +11,16 @@ import {
|
|||||||
import { Navigation, NavigationFunctionComponent } from 'react-native-navigation';
|
import { Navigation, NavigationFunctionComponent } from 'react-native-navigation';
|
||||||
import type { CameraDeviceFormat, CameraRuntimeError, PhotoFile, VideoFile } from 'react-native-vision-camera';
|
import type { CameraDeviceFormat, CameraRuntimeError, PhotoFile, VideoFile } from 'react-native-vision-camera';
|
||||||
import { Camera, frameRateIncluded, sortFormatsByResolution, filterFormatsByAspectRatio } 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 { CONTENT_SPACING, MAX_ZOOM_FACTOR, SAFE_AREA_PADDING } from './Constants';
|
||||||
import Reanimated, { Extrapolate, interpolate, useAnimatedGestureHandler, useAnimatedProps, useSharedValue } from 'react-native-reanimated';
|
import Reanimated, { Extrapolate, interpolate, useAnimatedGestureHandler, useAnimatedProps, useSharedValue } from 'react-native-reanimated';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useIsForeground } from './hooks/useIsForeground';
|
import { useIsForeground } from './hooks/useIsForeground';
|
||||||
import { StatusBarBlurBackground } from './views/StatusBarBlurBackground';
|
import { StatusBarBlurBackground } from './views/StatusBarBlurBackground';
|
||||||
import { CaptureButton } from './views/CaptureButton';
|
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 MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||||
import { useSelector } from 'pipestate';
|
|
||||||
import { FpsSelector } from './state/selectors';
|
|
||||||
import { useCameraDevice } from './hooks/useCameraDevice';
|
import { useCameraDevice } from './hooks/useCameraDevice';
|
||||||
|
|
||||||
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
||||||
@ -33,14 +31,14 @@ Reanimated.addWhitelistedNativeProps({
|
|||||||
const SCALE_FULL_ZOOM = 3;
|
const SCALE_FULL_ZOOM = 3;
|
||||||
const BUTTON_SIZE = 40;
|
const BUTTON_SIZE = 40;
|
||||||
|
|
||||||
export const App: NavigationFunctionComponent = ({ componentId }) => {
|
export const CameraPage: NavigationFunctionComponent = ({ componentId }) => {
|
||||||
const camera = useRef<Camera>(null);
|
const camera = useRef<Camera>(null);
|
||||||
const [isCameraInitialized, setIsCameraInitialized] = useState(false);
|
const [isCameraInitialized, setIsCameraInitialized] = useState(false);
|
||||||
const zoom = useSharedValue(0);
|
const zoom = useSharedValue(0);
|
||||||
const isPressingButton = useSharedValue(false);
|
const isPressingButton = useSharedValue(false);
|
||||||
|
|
||||||
// check if camera page is active
|
// check if camera page is active
|
||||||
const isFocussed = useIsScreenFocused(componentId);
|
const isFocussed = useIsScreenFocussed(componentId);
|
||||||
const isForeground = useIsForeground();
|
const isForeground = useIsForeground();
|
||||||
const isActive = isFocussed && isForeground;
|
const isActive = isFocussed && isForeground;
|
||||||
|
|
||||||
@ -59,32 +57,34 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
|||||||
}, [device?.formats]);
|
}, [device?.formats]);
|
||||||
|
|
||||||
//#region Memos
|
//#region Memos
|
||||||
const [targetFps] = useSelector(FpsSelector);
|
const [is60Fps, setIs60Fps] = useState(true);
|
||||||
console.log(`Target FPS: ${targetFps}`);
|
|
||||||
const fps = useMemo(() => {
|
const fps = useMemo(() => {
|
||||||
|
if (!is60Fps) return 30;
|
||||||
|
|
||||||
if (enableNightMode && !device?.supportsLowLightBoost) {
|
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.
|
// User has enabled Night Mode, but Night Mode is not natively supported, so we simulate it by lowering the frame rate.
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportsHdrAtHighFps = formats.some((f) => f.supportsVideoHDR && f.frameRateRanges.some((r) => frameRateIncluded(r, targetFps)));
|
const supportsHdrAt60Fps = formats.some((f) => f.supportsVideoHDR && f.frameRateRanges.some((r) => frameRateIncluded(r, 60)));
|
||||||
if (enableHdr && !supportsHdrAtHighFps) {
|
if (enableHdr && !supportsHdrAt60Fps) {
|
||||||
// User has enabled HDR, but HDR is not supported at targetFps.
|
// User has enabled HDR, but HDR is not supported at 60 FPS.
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportsHighFps = formats.some((f) => f.frameRateRanges.some((r) => frameRateIncluded(r, targetFps)));
|
const supports60Fps = formats.some((f) => f.frameRateRanges.some((r) => frameRateIncluded(r, 60)));
|
||||||
if (!supportsHighFps) {
|
if (!supports60Fps) {
|
||||||
// targetFps is not supported by any format.
|
// 60 FPS is not supported by any format.
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
// If nothing blocks us from using it, we default to targetFps.
|
// If nothing blocks us from using it, we default to 60 FPS.
|
||||||
return targetFps;
|
return 60;
|
||||||
}, [device?.supportsLowLightBoost, enableHdr, enableNightMode, formats, targetFps]);
|
}, [device?.supportsLowLightBoost, enableHdr, enableNightMode, formats, is60Fps]);
|
||||||
|
|
||||||
const supportsCameraFlipping = useMemo(() => devices.back != null && devices.front != null, [devices.back, devices.front]);
|
const supportsCameraFlipping = useMemo(() => devices.back != null && devices.front != null, [devices.back, devices.front]);
|
||||||
const supportsFlash = device?.hasFlash ?? false;
|
const supportsFlash = device?.hasFlash ?? false;
|
||||||
const supportsHdr = useMemo(() => formats.some((f) => f.supportsVideoHDR), [formats]);
|
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
|
const canToggleNightMode = enableNightMode
|
||||||
? true // it's enabled so you have to be able to turn it off again
|
? 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
|
: (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)}`);
|
console.log(`Media captured! ${JSON.stringify(media)}`);
|
||||||
await Navigation.showModal({
|
await Navigation.showModal({
|
||||||
component: {
|
component: {
|
||||||
name: 'Media',
|
name: 'MediaPage',
|
||||||
passProps: {
|
passProps: {
|
||||||
type: type,
|
type: type,
|
||||||
path: media.path,
|
path: media.path,
|
||||||
@ -147,18 +147,9 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
|||||||
const onFlipCameraPressed = useCallback(() => {
|
const onFlipCameraPressed = useCallback(() => {
|
||||||
setCameraPosition((p) => (p === 'back' ? 'front' : 'back'));
|
setCameraPosition((p) => (p === 'back' ? 'front' : 'back'));
|
||||||
}, []);
|
}, []);
|
||||||
const onHdrSwitchPressed = useCallback(() => {
|
|
||||||
setEnableHdr((h) => !h);
|
|
||||||
}, []);
|
|
||||||
const onFlashPressed = useCallback(() => {
|
const onFlashPressed = useCallback(() => {
|
||||||
setFlash((f) => (f === 'off' ? 'on' : 'off'));
|
setFlash((f) => (f === 'off' ? 'on' : 'off'));
|
||||||
}, []);
|
}, []);
|
||||||
const onNightModePressed = useCallback(() => {
|
|
||||||
setEnableNightMode((n) => !n);
|
|
||||||
}, []);
|
|
||||||
const onSettingsPressed = useCallback(() => {
|
|
||||||
Navigation.push(componentId, { component: { name: 'Settings' } });
|
|
||||||
}, [componentId]);
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Tap Gesture
|
//#region Tap Gesture
|
||||||
@ -269,19 +260,24 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
|||||||
<IonIcon name={flash === 'on' ? 'flash' : 'flash-off'} color="white" size={24} />
|
<IonIcon name={flash === 'on' ? 'flash' : 'flash-off'} color="white" size={24} />
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
)}
|
)}
|
||||||
{canToggleNightMode && (
|
{supports60Fps && (
|
||||||
<PressableOpacity style={styles.button} onPress={onNightModePressed} disabledOpacity={0.4}>
|
<PressableOpacity style={styles.button} onPress={() => setIs60Fps(!is60Fps)}>
|
||||||
<IonIcon name={enableNightMode ? 'moon' : 'moon-outline'} color="white" size={24} />
|
<Text style={styles.text}>
|
||||||
|
{is60Fps ? '60' : '30'}
|
||||||
|
{'\n'}FPS
|
||||||
|
</Text>
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
)}
|
)}
|
||||||
{supportsHdr && (
|
{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} />
|
<MaterialIcon name={enableHdr ? 'hdr' : 'hdr-off'} color="white" size={24} />
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
)}
|
)}
|
||||||
<PressableOpacity style={styles.button} onPress={onSettingsPressed}>
|
{canToggleNightMode && (
|
||||||
<IonIcon name="settings-outline" color="white" size={24} />
|
<PressableOpacity style={styles.button} onPress={() => setEnableNightMode(!enableNightMode)} disabledOpacity={0.4}>
|
||||||
</PressableOpacity>
|
<IonIcon name={enableNightMode ? 'moon' : 'moon-outline'} color="white" size={24} />
|
||||||
|
</PressableOpacity>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@ -311,4 +307,10 @@ const styles = StyleSheet.create({
|
|||||||
right: SAFE_AREA_PADDING.paddingRight,
|
right: SAFE_AREA_PADDING.paddingRight,
|
||||||
top: SAFE_AREA_PADDING.paddingTop,
|
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 Video, { LoadError, OnLoadData } from 'react-native-video';
|
||||||
import { SAFE_AREA_PADDING } from './Constants';
|
import { SAFE_AREA_PADDING } from './Constants';
|
||||||
import { useIsForeground } from './hooks/useIsForeground';
|
import { useIsForeground } from './hooks/useIsForeground';
|
||||||
import { useIsScreenFocused } from './hooks/useIsScreenFocused';
|
import { useIsScreenFocussed } from './hooks/useIsScreenFocused';
|
||||||
import { PressableOpacity } from './views/PressableOpacity';
|
import { PressableOpacity } from 'react-native-pressable-opacity';
|
||||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import CameraRoll from '@react-native-community/cameraroll';
|
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 =>
|
const isVideoOnLoadEvent = (event: OnLoadData | NativeSyntheticEvent<ImageLoadEventData>): event is OnLoadData =>
|
||||||
'duration' in event && 'naturalSize' in event;
|
'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 [hasMediaLoaded, setHasMediaLoaded] = useState(false);
|
||||||
const isForeground = useIsForeground();
|
const isForeground = useIsForeground();
|
||||||
const isScreenFocused = useIsScreenFocused(componentId);
|
const isScreenFocused = useIsScreenFocussed(componentId);
|
||||||
const isVideoPaused = !isForeground || !isScreenFocused;
|
const isVideoPaused = !isForeground || !isScreenFocused;
|
||||||
const [savingState, setSavingState] = useState<'none' | 'saving' | 'saved'>('none');
|
const [savingState, setSavingState] = useState<'none' | 'saving' | 'saved'>('none');
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ export const Media: NavigationFunctionComponent<MediaProps> = ({ componentId, ty
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Media.options = {
|
MediaPage.options = {
|
||||||
modal: {
|
modal: {
|
||||||
swipeToDismiss: false,
|
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 { useEffect } from 'react';
|
||||||
import { AppState, AppStateStatus } from 'react-native';
|
import { AppState, AppStateStatus } from 'react-native';
|
||||||
import { useCachedState } from './useCachedState';
|
|
||||||
|
|
||||||
export const useIsForeground = (): boolean => {
|
export const useIsForeground = (): boolean => {
|
||||||
const [isForeground, setIsForeground] = useCachedState(true);
|
const [isForeground, setIsForeground] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onChange = (state: AppStateStatus): void => {
|
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';
|
import { Navigation } from 'react-native-navigation';
|
||||||
|
|
||||||
type Action =
|
export const useIsScreenFocussed = (componentId: string): boolean => {
|
||||||
| {
|
const componentStack = useRef<string[]>(['componentId']);
|
||||||
action: 'push';
|
const [isFocussed, setIsFocussed] = useState(true);
|
||||||
componentId: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
action: 'pop';
|
|
||||||
componentId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (stack: string[], action: Action): string[] => {
|
const invalidate = useCallback(() => {
|
||||||
switch (action.action) {
|
const last = componentStack.current[componentStack.current.length - 1];
|
||||||
case 'push': {
|
setIsFocussed(last === componentId);
|
||||||
stack.push(action.componentId);
|
}, [componentId, setIsFocussed]);
|
||||||
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]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = Navigation.events().registerComponentDidAppearListener((event) => {
|
const listener = Navigation.events().registerComponentDidAppearListener((event) => {
|
||||||
if (event.componentType !== 'Component') return;
|
if (event.componentType !== 'Component') return;
|
||||||
dispatch({
|
componentStack.current.push(event.componentId);
|
||||||
action: 'push',
|
invalidate();
|
||||||
componentId: event.componentId,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => listener.remove();
|
return () => listener.remove();
|
||||||
}, []);
|
}, [invalidate]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = Navigation.events().registerComponentDidDisappearListener((event) => {
|
const listener = Navigation.events().registerComponentDidDisappearListener((event) => {
|
||||||
if (event.componentType !== 'Component') return;
|
if (event.componentType !== 'Component') return;
|
||||||
dispatch({
|
// we can't simply use .pop() here because the component might be popped deeper down in the hierarchy.
|
||||||
action: 'pop',
|
for (let i = componentStack.current.length - 1; i >= 0; i--) {
|
||||||
componentId: event.componentId,
|
if (componentStack.current[i] === event.componentId) {
|
||||||
});
|
componentStack.current.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => listener.remove();
|
return () => listener.remove();
|
||||||
}, []);
|
}, [invalidate]);
|
||||||
|
|
||||||
return isFocussed;
|
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"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
|
||||||
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
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:
|
pirates@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
|
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"
|
react-lifecycles-compat "2.0.0"
|
||||||
tslib "1.9.3"
|
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:
|
react-native-reanimated@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.1.0.tgz#b9ad04aee490e1e030d0a6cdaa43a14895d9a54d"
|
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;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SCREEN_SIZE: Size = {
|
const SCREEN_SIZE: Size = {
|
||||||
width: Dimensions.get('window').width,
|
width: Dimensions.get('window').width,
|
||||||
height: Dimensions.get('window').height,
|
height: Dimensions.get('window').height,
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyScaledMask = (
|
const applyScaledMask = (
|
||||||
clippedElementDimensions: Size, // 12 x 12
|
clippedElementDimensions: Size, // 12 x 12
|
||||||
maskDimensions: Size, // 6 x 12
|
maskDimensions: Size, // 6 x 12
|
||||||
@ -54,17 +56,11 @@ const applyScaledMask = (
|
|||||||
const wScale = maskDimensions.width / clippedElementDimensions.width; // 0.5
|
const wScale = maskDimensions.width / clippedElementDimensions.width; // 0.5
|
||||||
const hScale = maskDimensions.height / clippedElementDimensions.height; // 1.0
|
const hScale = maskDimensions.height / clippedElementDimensions.height; // 1.0
|
||||||
|
|
||||||
if (wScale > hScale) {
|
const scale = Math.min(wScale, hScale);
|
||||||
return {
|
return {
|
||||||
width: maskDimensions.width / hScale,
|
width: maskDimensions.width / scale,
|
||||||
height: maskDimensions.height / hScale,
|
height: maskDimensions.height / scale,
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
width: maskDimensions.width / wScale,
|
|
||||||
height: maskDimensions.height / wScale,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFormatAspectRatioOverflow = (format: CameraDeviceFormat, size: Size): number => {
|
const getFormatAspectRatioOverflow = (format: CameraDeviceFormat, size: Size): number => {
|
||||||
|
Loading…
Reference in New Issue
Block a user