1import * as ScreenOrientation from 'expo-screen-orientation'; 2import { Accelerometer } from 'expo-sensors'; 3import React from 'react'; 4import { 5 ActivityIndicator, 6 Animated, 7 Button, 8 Dimensions, 9 StyleSheet, 10 Text, 11 View, 12} from 'react-native'; 13 14import { Colors } from '../constants'; 15 16const COUNT = 5; 17const ITEM_SIZE = Dimensions.get('window').width / COUNT; 18 19interface Props { 20 numItems: number; 21 perspective: number; 22} 23 24function useLockedScreenOrientation() { 25 React.useEffect(() => { 26 ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => null); 27 return () => { 28 ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.ALL).catch(() => null); 29 }; 30 }, []); 31} 32 33export default function AccelerometerScreen({ numItems = COUNT, perspective = 200 }: Props) { 34 useLockedScreenOrientation(); 35 36 const [items, setItems] = React.useState<any[]>([]); 37 const [error, setError] = React.useState<string | null>(null); 38 const [isSetup, setSetup] = React.useState<boolean>(false); 39 40 React.useEffect(() => { 41 const items = []; 42 for (let i = 0; i < numItems; i++) { 43 items.push({ position: new Animated.ValueXY() }); 44 } 45 setItems(items); 46 }, [numItems]); 47 48 React.useEffect(() => { 49 (async () => { 50 const { status } = await Accelerometer.getPermissionsAsync(); 51 if (status === 'denied') { 52 setError(`Cannot start demo!\nMotion permission is ${status}.`); 53 } else if (status === 'undetermined') { 54 return; 55 } 56 if (!(await Accelerometer.isAvailableAsync())) { 57 setError('Accelerometer is not available on this device!'); 58 return; 59 } 60 61 setSetup(true); 62 })(); 63 }, []); 64 65 React.useEffect(() => { 66 if (!isSetup) return; 67 68 const sub = Accelerometer.addListener(({ x, y }) => { 69 // console.log('event'); 70 items.forEach((_, index) => { 71 // All that matters is that the values are the same on iOS, Android, Web, ect... 72 const nIndex = index + 1; 73 74 Animated.spring(items[index].position, { 75 toValue: { 76 x: (Number(x.toFixed(1)) * perspective * nIndex) / COUNT, 77 y: (-y.toFixed(1) * perspective * nIndex) / COUNT, 78 }, 79 useNativeDriver: false, 80 friction: 7, 81 }).start(); 82 }); 83 }); 84 return () => sub.remove(); 85 }, [isSetup]); 86 87 if (error) { 88 return ( 89 <Container> 90 <Text style={[styles.text, { color: 'red' }]}>{error}</Text> 91 </Container> 92 ); 93 } 94 95 if (!isSetup) { 96 return ( 97 <Container> 98 <ActivityIndicator size="large" color={Colors.tintColor} /> 99 <Text 100 style={[ 101 styles.text, 102 { 103 marginTop: 16, 104 }, 105 ]}> 106 Checking Permissions 107 </Text> 108 109 <Button 110 title="Ask Permission" 111 onPress={async () => { 112 const { status } = await Accelerometer.requestPermissionsAsync(); 113 if (status !== 'granted') { 114 setError(`Cannot start demo!\nMotion permission is ${status}.`); 115 } 116 if (!(await Accelerometer.isAvailableAsync())) { 117 setError('Accelerometer is not available on this device!'); 118 return; 119 } 120 121 setSetup(true); 122 }} 123 /> 124 </Container> 125 ); 126 } 127 128 return ( 129 <Container> 130 <Text style={[styles.text, styles.message]}> 131 {`The stack should move against the orientation of the device. 132 If you lift the bottom of the phone up, the stack should translate down towards the bottom of the screen. 133 The balls all line up when the phone is in "display up" mode.`} 134 </Text> 135 {items.map((val, index) => { 136 return ( 137 <Animated.View 138 key={`item-${index}`} 139 style={[ 140 styles.ball, 141 { 142 opacity: (index + 1) / COUNT, 143 transform: [ 144 { translateX: items[index].position.x }, 145 { translateY: items[index].position.y }, 146 ], 147 }, 148 ]} 149 /> 150 ); 151 })} 152 </Container> 153 ); 154} 155 156AccelerometerScreen.navigationOptions = { 157 title: 'Accelerometer', 158}; 159 160const Container = (props: any) => <View {...props} style={styles.container} />; 161 162const styles = StyleSheet.create({ 163 container: { 164 flex: 1, 165 alignItems: 'center', 166 justifyContent: 'center', 167 }, 168 text: { 169 zIndex: 1, 170 fontWeight: '800', 171 color: Colors.tintColor, 172 textAlign: 'center', 173 }, 174 message: { 175 position: 'absolute', 176 top: 24, 177 left: 24, 178 right: 24, 179 }, 180 ball: { 181 position: 'absolute', 182 width: ITEM_SIZE, 183 height: ITEM_SIZE, 184 borderRadius: ITEM_SIZE, 185 backgroundColor: 'red', 186 }, 187}); 188