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