React Native Expo Camera Ratio and Preview not configurable enough? - javascript

Hello all,
I have looked at a lot of other posts related to this issue but my question isn't simply how to put the correct preview dimensions based on the aspect ratio chosen. Im trying to figure out how Snapchat is able to consistently have a full screen camera preview expanding all the way from the status bar to the bottom of the screen. Ive used const ratios = await cameraRef.current.getSupportedRatiosAsync() to get the aspect ratios available for my test device and the highest one is 16:9, which doesn't give me enough height to have a preview that covers the entire screen and doesn't look distorted and out of the ordinary. In the android OS camera app theres an aspect ratio called 'full' which takes all the realestate available on the screen to show the camera preview, but the aspect ratios getSupportedRatiosAsync() returns do not include this 'full' aspect ratio. Im trying to achieve this flush look where the camera preview takes the entire screen height available and does not distort, but I end up having the black bar at the bottom because I have to take into account the aspect ratio of the camera and put the height of the preview acordingly. Is there something i'm missing here? When I simply put a flex:1 for the camera preview it takes over the screen but it ends up being a distorted preview because it's not fitting the aspect ratio of the camera.
import React, { useState, useRef, useEffect } from 'react'
import {
View,
Text,
StyleSheet,
Dimensions,
Modal,
ImageBackground,
Animated,
BackHandler,
} from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler'
//expo camera
import { Camera } from 'expo-camera'
//expo AV
import { Video, AVPlaybackStatus } from 'expo-av'
//custom components
import HeaderX from '../components/HeaderX'
import CameraButton from '../components/CameraButton'
//ionicons
import { Entypo, Ionicons } from '#expo/vector-icons'
import { Icon } from 'react-native-elements'
//ionicons
//colors
import colors from '../constants/colors'
//safe area
import { useSafeAreaInsets } from 'react-native-safe-area-context'
//redux
import { takePicture } from '../store/camera/actions'
import { useDispatch, useSelector } from 'react-redux'
// MediaLibrary
import * as MediaLibrary from 'expo-media-library'
//gesture handlers
import {
PinchGestureHandler,
PinchGestureHandlerGestureEvent,
State,
TapGestureHandler,
TapGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler'
import Reanimated, {
Extrapolate,
interpolate,
useAnimatedGestureHandler,
useAnimatedProps,
useSharedValue,
useAnimatedStyle,
runOnJS,
runOnUI,
} from 'react-native-reanimated'
//nav 5
import { useFocusEffect } from '#react-navigation/native'
//status bar
import { StatusBar } from 'expo-status-bar'
const { height, width } = Dimensions.get('window')
console.log('🚀 ~ file: CameraScreen.js ~ line 68 ~ height', height)
console.log('🚀 ~ file: CameraScreen.js ~ line 68 ~ width', width)
const CameraScreen = ({ navigation, route }) => {
let checkMarkSet = null
if (route.params) {
checkMarkSet = true
}
// RATIO SETTER
const [imagePadding, setImagePadding] = useState(0)
const [ratio, setRatio] = useState('4:3') // default is 4:3
const screenRatio = height / width
const [isRatioSet, setIsRatioSet] = useState(false)
async function prepareRatio() {
let desiredRatio = '4:3' // Start with the system default
// This issue only affects Android
if (Platform.OS === 'android') {
const ratios = await cameraRef.current.getSupportedRatiosAsync()
let distances = {}
let realRatios = {}
let minDistance = null
for (const ratio of ratios) {
const parts = ratio.split(':')
const ratioHeight = parseInt(parts[0])
const ratioWidth = parseInt(parts[1])
const realRatio = ratioHeight / ratioWidth
realRatios[ratio] = realRatio
// ratio can't be taller than screen, so we don't want an abs()
const distance = screenRatio - realRatio
distances[ratio] = realRatio
if (minDistance == null) {
minDistance = ratio
} else {
if (distance >= 0 && distance < distances[minDistance]) {
minDistance = ratio
}
}
}
// set the best match
desiredRatio = minDistance
// calculate the difference between the camera width and the screen height
const remainder = Math.floor(
(height - realRatios[desiredRatio] * width) / 2
)
// set the preview padding and preview ratio
setImagePadding(remainder / 2)
console.log(`okay look ${remainder / 2}`)
setRatio(desiredRatio)
// Set a flag so we don't do this
// calculation each time the screen refreshes
setIsRatioSet(true)
}
}
const setCameraReady = async () => {
if (!isRatioSet) {
await prepareRatio()
}
}
// RATIO SETTER
const [type, setType] = useState(Camera.Constants.Type.back)
const [activateCamera, setActivateCamera] = useState(false)
const [video, setVideo] = useState('')
const [showVideoModal, setShowVideoModal] = useState(false)
const insets = useSafeAreaInsets()
useFocusEffect(() => {
if (navigation.isFocused()) {
setActivateCamera(true)
}
})
const [pic, setPic] = useState(null)
const [showModal, setShowModal] = useState(false)
const cameraRef = useRef()
const dispatch = useDispatch()
const [zooming, setZooming] = useState(0)
//camera settings
const [flashMode, setFlashMode] = useState('off')
// const picTaken = useSelector((state) => state.cameraReducer.pictureUri)
// console.log(
// '🚀 ~ file: CameraScreen.js ~ line 36 ~ CameraScreen ~ picTaken',
// picTaken
// )
// camera Functions
async function takePictureHandler() {
try {
if (cameraRef.current) {
const options = {
quality: 0.5,
base64: true,
skipProcessing: true,
}
let photo = await cameraRef.current.takePictureAsync(options)
setPic(photo.uri)
dispatch(takePicture(photo.uri))
setShowModal(true)
}
} catch (err) {
console.log(err)
}
// setPickedImage(image.uri)
// props.onImageTaken(image.uri)
}
function flipCameraHandler() {
setType(
type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
)
}
function flashSwitchHandler() {
if (flashMode === 'off') {
setFlashMode('on')
}
if (flashMode === 'on') {
setFlashMode('off')
}
}
async function savePictureLocallyHandler(localUri) {
const { status } = await MediaLibrary.getPermissionsAsync()
if (status === 'undetermined') {
const { status } = await MediaLibrary.requestPermissionsAsync()
if (status === 'granted') {
const asset = await MediaLibrary.createAssetAsync(localUri)
}
}
if (status === 'granted') {
const asset = await MediaLibrary.createAssetAsync(localUri)
if (asset) {
//display check mark showing it was saved.
}
}
if (status === 'denied') {
console.log('Open settings and give permission')
}
}
// zoom gesture handler
const zoom = useSharedValue(0)
const MAX_ZOOM_FACTOR = 20
const SCALE_FULL_ZOOM = 20
const formatMaxZoom = 1
const maxZoomFactor = Math.min(formatMaxZoom, MAX_ZOOM_FACTOR)
const neutralZoomScaled = (neutralZoom / maxZoomFactor) * formatMaxZoom
const maxZoomScaled = (1 / formatMaxZoom) * maxZoomFactor
const neutralZoom = 0
useAnimatedProps(
() => ({
zoom: interpolate(
zoom.value,
[0, neutralZoomScaled, 1],
[0, neutralZoom, maxZoomScaled],
Extrapolate.CLAMP
),
}),
[maxZoomScaled, neutralZoom, neutralZoomScaled, zoom]
)
function updateValue() {
setZooming(zoom.value)
}
function willThisWork() {
'worklet'
runOnJS(updateValue)()
}
const onPinchGesture = useAnimatedGestureHandler({
onStart: (_, context) => {
context.startZoom = zoom.value
},
onActive: (event, context) => {
// trying to map the scale gesture to a linear zoom here
const startZoom = context.startZoom ?? 0
const scale = interpolate(
event.scale,
[1 - 1 / SCALE_FULL_ZOOM, 1, SCALE_FULL_ZOOM],
[-1, 0, 1],
Extrapolate.CLAMP
)
zoom.value = interpolate(
scale,
[-1, 0, 1],
[0, startZoom, 1],
Extrapolate.CLAMP
)
willThisWork()
},
})
// VIDEO RECORDING
async function beginRecording() {
console.log('started')
let video = await cameraRef.current.recordAsync()
setVideo(video)
// setPic(photo.uri)
// dispatch(takePicture(photo.uri))
}
async function endRecording() {
console.log('ended')
cameraRef.current.stopRecording()
setShowVideoModal(true)
}
return (
<View
style={{
...styles.container,
// paddingTop: Platform.OS === 'android' ? insets.top : null,
}}
>
<StatusBar
style=""
translucent
backgroundColor="rgba(255,255,255,0)"
/>
<PinchGestureHandler onGestureEvent={onPinchGesture}>
<Reanimated.View
style={{
flex: 1,
backgroundColor: 'back',
justifyContent: 'flex-start',
paddingBottom: imagePadding * 4,
}}
>
{activateCamera && (
<Camera
style={{
// marginTop: imagePadding,
// marginBottom: imagePadding,
flex: 1,
// height: 733,
}}
ref={cameraRef}
type={type}
flashMode={flashMode}
zoom={zooming}
onCameraReady={setCameraReady}
ratio={ratio}
maxDuration={10000}
autoFocus="on"
>
<View
style={[
styles.contentContainer,
{
paddingTop: insets.top,
paddingBottom: insets.bottom,
top: insets.top,
bottom: insets.bottom,
},
]}
>
<View style={styles.topLeftCont}>
<TouchableOpacity
onPress={flipCameraHandler}
>
<Entypo
name="loop"
size={27}
color="white"
style={styles.flipIcon}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={flashSwitchHandler}
>
<Ionicons
name={
flashMode !== 'off'
? 'flash'
: 'flash-off'
}
size={27}
color="white"
style={styles.cameraSettingsButton}
/>
</TouchableOpacity>
</View>
<CameraButton
style={{
...styles.floatingPlusCont,
left: width / 2 - 45,
}}
onLongPress={beginRecording}
onEndPress={endRecording}
onTap={takePictureHandler}
/>
</View>
</Camera>
)}
</Reanimated.View>
</PinchGestureHandler>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-start',
},
contentContainer: {
flex: 1,
position: 'absolute',
right: 0,
left: 0,
},
camera: {
flex: 1,
flexDirection: 'row',
},
topLeftCont: {
position: 'absolute',
width: 45,
top: 0,
right: 10,
borderRadius: 20,
backgroundColor: 'rgba(184,184,184,0.42)',
alignItems: 'center',
justifyContent: 'space-between',
// flexDirection: 'row',
padding: 5,
},
flipIcon: {
marginVertical: 7,
transform: [
{
rotate: '90deg',
},
],
},
cameraSettingsButton: { marginVertical: 7 },
modal: {
flex: 1,
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
},
takenImage: { flex: 1 },
bottomCont: {
flex: 1,
justifyContent: 'flex-end',
padding: 10,
},
bottomButtonsCont: {
width: '100%',
justifyContent: 'space-between',
flexDirection: 'row',
paddingHorizontal: 5,
},
floatingPlusCont: {
bottom: 25,
position: 'absolute',
width: 90,
height: 90,
borderRadius: 45,
},
loadingView: {
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
alignItems: 'center',
},
})
export default CameraScreen

Related

(React Native) How to keep animated component in new place after initial animation

I have a <TextInput> which, when the user enters anything into, will have a button appear from behind it.
This works fine using the Animated library from react, but each time I input text into the area, the animation "rubber bands" up and down.
My question is: How can I keep the button in place after the initial animation happens?
I still need to monitor whether the text box is empty in order to decide whether to show or hide the button.
My code:
import { View, TextInput, StyleSheet, Animated, TouchableOpacity } from "react-native";
import { useState } from "react";
import CurrencyInput from 'react-native-currency-input';
import { Ionicons } from "#expo/vector-icons";
type expenseItemData = {
item:string;
costAndSign:string;
cost:number | null;
}
type Props = {
sendItem: ({ item, cost, costAndSign }: expenseItemData) => void;
}
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
const AddAndConfirmExpense: React.FC<Props> = ({sendItem}) => {
const [animation] = useState(new Animated.Value(-58));
const [expenseValue, setExpenseValue] = useState<number>(0.00);
const [expenseItem, setExpenseItem] = useState<string>("");
const [expenseValueAndSign, SetExpenseValueAndSign] = useState<string>("");
const [buttonLayoutPersistence, setButtonLayoutPersistence] = useState({
bottom:0
});
const [validExpenseItem, setValidExpenseItem] = useState<boolean>(false);
const onChangeExpenseItem = (text: string) => {
setExpenseItem(text);
if (text.trim().length === 0) {
setValidExpenseItem(false);
hideButton();
setButtonLayoutPersistence({bottom:0})
return;
}
if (validExpenseItem) {
setButtonLayoutPersistence({bottom:-48})
return
};
showButton();
setValidExpenseItem(true);
};
const onButtonPress = () => {
const newData:expenseItemData = {
item:expenseItem,
costAndSign:expenseValueAndSign,
cost:expenseValue
}
sendItem(newData);
};
const setAreaDynamicStyling = () => {
if (validExpenseItem) {
return {
borderTopRightRadius:5,
borderTopLeftRadius:5,
backgroundColor:"#f5f5f5"
}
}
return {borderRadius:5};
};
const setButtonDynamicStyling = () => {
if (!validExpenseItem) return {borderRadius:5}
return {borderBottomLeftRadius: 5,borderBottomRightRadius:5}
};
const animatedStyle = {transform: [{translateY:animation}],};
const showButton = () => {
Animated.timing(animation, {
toValue: -10,
duration: 1000,
useNativeDriver: true,
}).start();
}
const hideButton = () => {
Animated.timing(animation, {
toValue: -58,
duration: 500,
useNativeDriver: true,
}).start();
}
return (
<View>
<View style={validExpenseItem ? [styles.inputsContainer, setAreaDynamicStyling()] : [styles.inputsContainer,styles.shadowProp,setAreaDynamicStyling()]}>
<TextInput
style={styles.textInputArea}
placeholder='Item'
placeholderTextColor="#aaaaaa"
onChangeText={(text) => {onChangeExpenseItem(text)}}
value={expenseItem}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<View style={styles.verticalLine}/>
<CurrencyInput
style={styles.currencyInputArea}
value={expenseValue}
onChangeValue={setExpenseValue}
prefix="£"
delimiter=","
separator="."
precision={2}
minValue={0}
onChangeText={(formattedValue) => {
SetExpenseValueAndSign(formattedValue)
}}
/>
</View>
<AnimatedTouchable onPress={()=>{onButtonPress()}} style={[{flex:1, zIndex:-1},animatedStyle]}>
<View style={[styles.confirmInputContainer, setButtonDynamicStyling(), buttonLayoutPersistence]}>
<Ionicons name="checkmark-circle-outline" size={24} color="white" />
</View>
</AnimatedTouchable>
</View>
)
}
const styles = StyleSheet.create({
confirmInputContainer:{
backgroundColor:"#7f96ff",
height: 48,
flexDirection:"row",
paddingVertical:10,
justifyContent:"center",
},
inputsContainer:{
backgroundColor:"white",
height: 48,
flexDirection:"row",
paddingVertical:10,
marginVertical:10,
justifyContent:"space-between",
},
shadowProp: {
shadowColor: '#353935',
shadowOffset: {width: -2, height: 4},
shadowOpacity: 0.2,
shadowRadius: 4,
},
textInputArea:{
width:"60%",
maxWidth:"60%",
marginLeft:20,
},
verticalLine:{
height: "100%",
width: 1,
backgroundColor: "#909090",
marginHorizontal:5,
},
currencyInputArea:{
maxWidth:"20%",
width:"20%",
marginRight:20
},
})
export default AddAndConfirmExpense;
EDIT:
I have added:
const [animationActive, setAnimationActive] = useState(false);
useEffect(() => {
if (!animationActive && validExpenseItem) {
setButtonLayoutPersistence({bottom:-48})
};
if (!animationActive && !validExpenseItem) {
setButtonLayoutPersistence({bottom:0})
}
},[animationActive])
And changed my show and hide functions to the following:
const showButton = () => {
if (animationActive) return;
setAnimationActive(true)
Animated.timing(animation, {
toValue: -10,
duration: 500,
useNativeDriver: true,
}).start(
() => {
setAnimationActive(false)
}
);
}
const hideButton = () => {
if (animationActive) return;
setAnimationActive(true)
Animated.timing(animation, {
toValue: -58,
duration: 500,
useNativeDriver: true,
}).start(
() => {
setAnimationActive(false)
}
);
}
I have also changed onChangeExpenseItem to :
const onChangeExpenseItem = (text: string) => {
setExpenseItem(text);
if (text.trim().length === 0) {
setValidExpenseItem(false);
hideButton();
setButtonLayoutPersistence({bottom:0})
return;
}
if (!validExpenseItem && !animationActive) {
setValidExpenseItem(true);
showButton();
return
};
};
It is slightly better now, but a better solution is still needed.

Flickering When Drawing Keypoints to Canvas

I am learning the the basics of tensorflow.js. I have been able to load a model successfully and extract keypoints from the webcam using blazepose. However, I am noticing that when I start to draw keypoints to the canvas, there is a lot of flickering. I am assuming this is due to the setInterval that does the rendering, but I'm not sure how to remedy this so that the keypoints are rendered in "real-time". Thanks for the help!
import './App.css';
import React, { useEffect, useRef } from 'react';
import * as poseDetection from '#tensorflow-models/pose-detection';
import '#tensorflow/tfjs-backend-webgl';
import Webcam from "react-webcam";
import { drawPoints } from './utils/drawKeyPoints';
function App() {
const webcamRef = useRef(null);
const canvasRef = useRef(null);
async function initPoseDetection() {
const model = poseDetection.SupportedModels.BlazePose;
const config = {
runtime: 'tfjs',
modelType: 'full',
maxPoses: 1,
}
const detector = await poseDetection.createDetector(model, config)
return detector;
}
async function start(){
const detector = await initPoseDetection();
setInterval(() => {
render(detector)
}, 100)
}
async function render(detector) {
if (
typeof webcamRef.current !== "undefined" &&
webcamRef.current !== null &&
webcamRef.current.video.readyState === 4
) {
// Get Video Properties
const video = webcamRef.current.video;
const videoWidth = webcamRef.current.video.videoWidth;
const videoHeight = webcamRef.current.video.videoHeight;
// Set video width
webcamRef.current.video.width = videoWidth;
webcamRef.current.video.height = videoHeight;
// Set canvas height and width
canvasRef.current.width = videoWidth;
canvasRef.current.height = videoHeight;
const ctx = canvasRef.current.getContext("2d");
const poses = await detector.estimatePoses(video)
drawPoints(ctx, poses[0]["keypoints"])
}
}
start()
return (
<div className="App">
<header className="App-header">
<Webcam
ref={webcamRef}
style={{
position: "absolute",
marginLeft: "auto",
marginRight: "auto",
left: 0,
right: 0,
textAlign: "center",
zindex: 9,
width: 1280,
height: 720,
}}
>
</Webcam>
<canvas
ref={canvasRef}
style={{
position: "absolute",
marginLeft: "auto",
marginRight: "auto",
left: 0,
right: 0,
textAlign: "center",
zindex: 9,
width: 1280,
height: 720,
}}
/>
</header>
</div>
);
}
export default App;

Why is my Animated.Image not rotating (React-Native)

I'm new to react so please be nice,
I'm trying to animate my compass, so that every time the userLocation is updated, the arrow (in my code the png of the animated image) is rotated at the given angle (here rotation) so that it points at another location. For some reason, it seems like the rotation passed to the Animated.Image remains 0, because the image never rotates. Can someone land me a hand real quick.
Here's my code:
import {
Alert,
Animated,
Easing,
Linking,
StyleSheet,
Text,
View,
} from "react-native";
import React, { useEffect, useRef, useState } from "react";
import * as Location from "expo-location";
import * as geolib from "geolib";
import { COLORS } from "../../assets/Colors/Colors";
export default function DateFinder() {
const [hasForegroundPermissions, setHasForegroundPermissions] =
useState(null);
const [userLocation, setUserLocation] = useState(null);
const [userHeading, setUserHeading] = useState(null);
const [angle, setAngle] = useState(0);
const rotation = useRef(new Animated.Value(0)).current;
useEffect(() => {
const AccessLocation = async () => {
function appSettings() {
console.warn("Open settigs pressed");
if (Platform.OS === "ios") {
Linking.openURL("app-settings:");
} else RNAndroidOpenSettings.appDetailsSettings();
}
const appSettingsALert = () => {
Alert.alert(
"Allow Wassupp to Use your Location",
"Open your app settings to allow Wassupp to access your current position. Without it, you won't be able to use the love compass",
[
{
text: "Cancel",
onPress: () => console.warn("Cancel pressed"),
},
{ text: "Open settings", onPress: appSettings },
]
);
};
const foregroundPermissions =
await Location.requestForegroundPermissionsAsync();
if (
foregroundPermissions.canAskAgain == false ||
foregroundPermissions.status == "denied"
) {
appSettingsALert();
}
setHasForegroundPermissions(foregroundPermissions.status === "granted");
if (foregroundPermissions.status == "granted") {
const location = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
activityType: Location.ActivityType.Fitness,
distanceInterval: 0,
},
(location) => {
setUserLocation(location);
}
);
const heading = await Location.watchHeadingAsync((heading) => {
setUserHeading(heading.trueHeading);
});
}
};
AccessLocation().catch(console.error);
}, []);
useEffect(() => {
if (userLocation != null) {
setAngle(getBearing() - userHeading);
rotateImage(angle); // Here's the call to the rotateImage function that should cause the value of rotation to be animated
}
}, [userLocation]);
const textPosition = JSON.stringify(userLocation);
const getBearing = () => {
const bearing = geolib.getGreatCircleBearing(
{
latitude: userLocation.coords.latitude,
longitude: userLocation.coords.longitude,
},
{
latitude: 45.47307231766645,
longitude: -73.86611198944459,
}
);
return bearing;
};
const rotateImage = (angle) => {
Animated.timing(rotation, {
toValue: angle,
duration: 1000,
easing: Easing.bounce,
useNativeDriver: true,
}).start();
};
return (
<View style={styles.background}>
<Text>{textPosition}</Text>
<Animated.Image
source={require("../../assets/Compass/Arrow_up.png")}
style={[styles.image, { transform: [{ rotate: `${rotation}deg` }] }]} // this is where it should get rotated but it doesn't for some reason
/>
</View>
);
}
const styles = StyleSheet.create({
background: {
backgroundColor: COLORS.background_Pale,
flex: 1,
// justifyContent: "flex-start",
//alignItems: "center",
},
image: {
flex: 1,
// height: null,
// width: null,
//alignItems: "center",
},
scrollView: {
backgroundColor: COLORS.background_Pale,
},
});
Your error is here
useEffect(() => {
if (userLocation != null) {
setAngle(getBearing() - userHeading);
rotateImage(angle); // Here's the call to the rotateImage function that should cause the value of rotation to be animated
}
}, [userLocation]);
The angle will be updated on the next render, so the rotation you do will always be a render behind. You could either store the result of getBearing and setAngle to that value as well as provide that value to rotateImage:
useEffect(() => {
if (userLocation != null) {
const a = getBearing() -userHeading;
setAngle(a);
rotateImage(a); // Here's the call to the rotateImage function that should cause the value of rotation to be animated
}
}, [userLocation]);
or you could use useEffect and listen for angle changes:
useEffect(() => {
rotateImage(angle)
}, [angle]);

The PanGestureHandler translationY resets to 0 once the gesture begins even though I am adding an offset value to it. How is that possible?

I have created a simple PanGestureHandler and want it to start from previous position when the gesture begins. I am setting the translationY value to the offsetY value when the gesture ends, which works perfectly and when the gesture begins I'm setting the sum of offsetY(which is the previous translationY) and the translationY to translationY, which on debugging shows the correct translation value. But that doesn't reflect on the View. Since I'm new to reanimated I don't know why that happens. I also couldn't find much resources on the implementation of gesture handlers using functional components.
Any ideas on how to fix this?
My Code:
import React from 'react'
import { Dimensions, Text } from 'react-native'
import { PanGestureHandler, State } from 'react-native-gesture-handler'
import Animated, { add, block, cond, debug, eq, event, Extrapolate, interpolate, set, useCode, useValue } from 'react-native-reanimated';
const {height,width}=Dimensions.get("window")
export default function Pan() {
const translationY = useValue(0)
const offsetY = useValue(0)
const gestureState = useValue(State.UNDETERMINED)
const onGestureEvent = event([{
nativeEvent: {
translationY,
state: gestureState
},
}], { useNativeDriver: true });
useCode(() => block([
cond(eq(gestureState, State.BEGAN), [set(translationY, add(translationY, offsetY)),debug('offsetY', translationY)]),
cond(eq(gestureState, State.END), [set(offsetY, translationY), debug('translateY', offsetY)])
]), [])
const translateY = translationY
return (
<PanGestureHandler {...{onGestureEvent}} onHandlerStateChange={onGestureEvent}>
<Animated.View style={{ height: height * 45 / 100, backgroundColor:'red', width: width, transform: [{ translateY }] }}>
<Text>PanGesture</Text>
</Animated.View>
</PanGestureHandler>
)
}
import React from "react";
import {Dimensions, Text, View} from "react-native";
import {PanGestureHandler} from "react-native-gesture-handler";
import Animated, {
Extrapolate,
useSharedValue,
useAnimatedGestureHandler,
interpolate,
useAnimatedStyle,
} from "react-native-reanimated";
const {height, width} = Dimensions.get("window");
const Test: React.FC = () => {
const translationY = useSharedValue(0);
const onGestureEvent = useAnimatedGestureHandler(
{
onStart: (_, ctx) => {
ctx.y = translationY.value;
},
onActive: (event, ctx) => {
translationY.value = event.translationY + ctx.y;
},
},
[translationY.value],
);
const animatedStyles = useAnimatedStyle(() => {
const translateY = interpolate(
translationY.value,
[0, height - (height * 45) / 100],
[0, height - (height * 45) / 100],
Extrapolate.CLAMP,
);
return {
height: (height * 45) / 100,
backgroundColor: "red",
width,
transform: [{translateY}],
};
}, [translationY.value]);
return (
<View style={{height, width, backgroundColor: "yellow"}}>
<PanGestureHandler {...{onGestureEvent}}>
<Animated.View style={animatedStyles}>
<Text>PanGesture</Text>
</Animated.View>
</PanGestureHandler>
</View>
);
};
export default Test;

Invalid YGDisplay 'inline '. should be one of: ( flex, none)

getting this weird error when I try to open a post in my app that contains photos inside html webview, the app is a react native app and retrieve posts from wordpress backend
https://i.imgur.com/738mF4Y.png
changed the view to inline with no success
my webview index.js
/** #format */
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import {
View,
Image,
Dimensions,
Linking,
WebView,
WebBrowser,
} from 'react-native'
import HTML from 'react-native-render-html'
import { Tools, Constants, error, warn, Config } from '#common'
import sanitizeHtml from 'sanitize-html'
const { height: PageHeight, width: PageWidth } = Dimensions.get('window')
export default class Index extends PureComponent {
static propTypes = {
html: PropTypes.any,
}
constructor(props) {
super(props)
this.state = { fontSize: Constants.fontText.size }
}
async componentWillMount() {
const fontSize = await Tools.getFontSizePostDetail()
this.setState({
fontSize,
})
}
onLinkPress = (url) => {
if (typeof WebBrowser !== 'undefined') {
WebBrowser.openBrowserAsync(url)
} else {
Linking.canOpenURL(url)
.then((supported) => {
if (!supported) {
} else {
return Linking.openURL(url)
}
})
.catch((err) => error('An error occurred', err))
}
}
render() {
const htmlContent = Config.EnableSanitizeHtml ? sanitizeHtml(this.props.html, {
allowedTags: [ 'b', 'p', 'i', 'img', 'em', 'strong', 'a' ],
allowedAttributes: {
'a': [ 'href' ],
'img' : ['src', 'alt', 'width', 'height']
},
allowedIframeHostnames: ['www.youtube.com']
}) : this.props.html
const fontSize = this.state.fontSize
? this.state.fontSize
: Constants.fontText.size
const tagsStyles = {
a: { color: '#333', fontSize },
strong: { color: '#333', fontSize, fontWeight: '700' },
p: { color: '#333', marginBottom: 5, fontSize, lineHeight: 24 },
em: { fontStyle: 'italic', fontSize },
video: { marginBottom: 5 },
img: { resizeMode: 'cover' },
ul: { color: '#333' },
li: { color: '#333' },
}
const renderers = {
img: (htmlAttribs, children, convertedCSSStyles, passProps) => {
const { src, width, height } = htmlAttribs
if (!src) {
return false
}
const newWidth = Dimensions.get('window').width - 20
const newHeight = height * newWidth / width
return (
<Image
key={passProps.key}
source={{ uri: src }}
style={{
width: newWidth,
height: newHeight,
resizeMode: 'contain',
}}
/>
)
},
iframe: (htmlAttribs, children, convertedCSSStyles, passProps) => {
if (htmlAttribs.src) {
const newWidth = PageWidth
const width = htmlAttribs.width
const height = htmlAttribs.height
const newHeight = height > 0 ? height * newWidth / width : width * 0.7
const url = htmlAttribs.src
return (
<WebView
renderLoading={() => <ActivityIndicator animating size="large" />}
originWhitelist={['*']}
canOpenURL={true}
key={`webview-${passProps.key}`}
source={{ uri: url }}
allowsInlineMediaPlayback={true}
mediaPlaybackRequiresUserAction={true}
javaScriptEnabled
scrollEnabled={false}
automaticallyAdjustContentInsets
style={{
width: PageWidth,
left: -12,
height: newHeight + 15,
}}
/>
)
}
},
}
// warn(['content:', htmlContent])
return (
<View style={{
padding: 12 }}>
<HTML
canOpenURL={true}
html={Constants.RTL ? `<div style="text-align: left;">${htmlContent}</div>` : htmlContent}
ignoredStyles={['font-family']}
renderers={renderers}
imagesMaxWidth={PageWidth}
tagsStyles={tagsStyles}
onLinkPress={(evt, href) => this.onLinkPress(href)}
staticContentMaxWidth={PageWidth}
/>
</View>
)
}
}
I am getting this error only with posts that has images inside the webview
I fixed this issue by using this prop allowedStyles={[]} in the Component
#Khuser Some of the web css styles are not supported by the HTML Renderer in this dependency that is why you are seeing this error.
Pls check the css styles in the HTML content and then ignore the styles which are not supported, like below by using the prop:
ignoredStyles={['font-family', 'display']}
This styles will be ignored.
Like mentioned in the previous answer you can also add this prop,
allowedStyles={[]}
This will help you fix the issue.

Categories