1import React from 'react'; 2import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 3import * as Sensors from 'expo-sensors'; 4import { Subscription } from '@unimodules/core'; 5 6const FAST_INTERVAL = 16; 7const SLOW_INTERVAL = 1000; 8 9export default class SensorScreen extends React.Component { 10 static navigationOptions = { 11 title: 'Sensors', 12 }; 13 14 render() { 15 return ( 16 <ScrollView style={styles.container}> 17 <GyroscopeSensor /> 18 <AccelerometerSensor /> 19 <MagnetometerSensor /> 20 <MagnetometerUncalibratedSensor /> 21 <BarometerSensor /> 22 <DeviceMotionSensor /> 23 </ScrollView> 24 ); 25 } 26} 27 28interface State<M extends object> { 29 data: M; 30 isAvailable?: boolean; 31} 32 33abstract class SensorBlock<M extends object> extends React.Component<{}, State<M>> { 34 readonly state: State<M> = { data: {} as M }; 35 36 _subscription?: Subscription; 37 38 componentDidMount() { 39 this.checkAvailability(); 40 } 41 42 checkAvailability = async () => { 43 const isAvailable = await this.getSensor().isAvailableAsync(); 44 this.setState({ isAvailable }); 45 }; 46 47 componentWillUnmount() { 48 this._unsubscribe(); 49 } 50 51 abstract getName: () => string; 52 abstract getSensor: () => Sensors.DeviceSensor<M>; 53 abstract renderData: () => JSX.Element; 54 55 _toggle = () => { 56 if (this._subscription) { 57 this._unsubscribe(); 58 } else { 59 this._subscribe(); 60 } 61 }; 62 63 _slow = () => { 64 this.getSensor().setUpdateInterval(SLOW_INTERVAL); 65 }; 66 67 _fast = () => { 68 this.getSensor().setUpdateInterval(FAST_INTERVAL); 69 }; 70 71 _subscribe = () => { 72 this._subscription = this.getSensor().addListener((data: any) => { 73 this.setState({ data }); 74 }); 75 }; 76 77 _unsubscribe = () => { 78 this._subscription && this._subscription.remove(); 79 this._subscription = undefined; 80 }; 81 82 render() { 83 if (this.state.isAvailable !== true) { 84 return null; 85 } 86 return ( 87 <View style={styles.sensor}> 88 <Text>{this.getName()}:</Text> 89 {this.renderData()} 90 <View style={styles.buttonContainer}> 91 <TouchableOpacity onPress={this._toggle} style={styles.button}> 92 <Text>Toggle</Text> 93 </TouchableOpacity> 94 <TouchableOpacity onPress={this._slow} style={[styles.button, styles.middleButton]}> 95 <Text>Slow</Text> 96 </TouchableOpacity> 97 <TouchableOpacity onPress={this._fast} style={styles.button}> 98 <Text>Fast</Text> 99 </TouchableOpacity> 100 </View> 101 </View> 102 ); 103 } 104} 105 106abstract class ThreeAxisSensorBlock extends SensorBlock<Sensors.ThreeAxisMeasurement> { 107 renderData = () => ( 108 <Text> 109 x: {round(this.state.data.x)} y: {round(this.state.data.y)} z: {round(this.state.data.z)} 110 </Text> 111 ); 112} 113 114class GyroscopeSensor extends ThreeAxisSensorBlock { 115 getName = () => 'Gyroscope'; 116 getSensor = () => Sensors.Gyroscope; 117} 118 119class AccelerometerSensor extends ThreeAxisSensorBlock { 120 getName = () => 'Accelerometer'; 121 getSensor = () => Sensors.Accelerometer; 122} 123 124class MagnetometerSensor extends ThreeAxisSensorBlock { 125 getName = () => 'Magnetometer'; 126 getSensor = () => Sensors.Magnetometer; 127} 128 129class MagnetometerUncalibratedSensor extends ThreeAxisSensorBlock { 130 getName = () => 'Magnetometer (Uncalibrated)'; 131 getSensor = () => Sensors.MagnetometerUncalibrated; 132} 133 134class DeviceMotionSensor extends SensorBlock<Sensors.DeviceMotionMeasurement> { 135 getName = () => 'DangerZone.DeviceMotion'; 136 getSensor = () => Sensors.DeviceMotion; 137 renderXYZBlock = (name: string, { x, y, z }: { x?: number; y?: number; z?: number } = {}) => ( 138 <Text> 139 {name}: x: {round(x)} y: {round(y)} z: {round(z)} 140 </Text> 141 ); 142 renderABGBlock = ( 143 name: string, 144 { alpha, beta, gamma }: { alpha?: number; beta?: number; gamma?: number } = {} 145 ) => ( 146 <Text> 147 {name}: α: {round(alpha)} β: {round(beta)} γ: {round(gamma)} 148 </Text> 149 ); 150 renderData = () => ( 151 <View> 152 {this.renderXYZBlock('Acceleration', this.state.data.acceleration)} 153 {this.renderXYZBlock('Acceleration w/gravity', this.state.data.accelerationIncludingGravity)} 154 {this.renderABGBlock('Rotation', this.state.data.rotation)} 155 {this.renderABGBlock('Rotation rate', this.state.data.rotationRate)} 156 <Text>Orientation: {this.state.data.orientation}</Text> 157 </View> 158 ); 159} 160 161class BarometerSensor extends SensorBlock<Sensors.BarometerMeasurement> { 162 getName = () => 'Barometer'; 163 getSensor = () => Sensors.Barometer; 164 renderData = () => ( 165 <View> 166 <Text>Pressure: {this.state.data.pressure}</Text> 167 <Text>Relative Altitude: {this.state.data.relativeAltitude}</Text> 168 </View> 169 ); 170} 171 172function round(n?: number) { 173 if (!n) { 174 return 0; 175 } 176 177 return Math.floor(n * 100) / 100; 178} 179 180const styles = StyleSheet.create({ 181 container: { 182 flex: 1, 183 marginBottom: 10, 184 }, 185 buttonContainer: { 186 flexDirection: 'row', 187 alignItems: 'stretch', 188 marginTop: 15, 189 }, 190 button: { 191 flex: 1, 192 justifyContent: 'center', 193 alignItems: 'center', 194 backgroundColor: '#eee', 195 padding: 10, 196 }, 197 middleButton: { 198 borderLeftWidth: 1, 199 borderRightWidth: 1, 200 borderColor: '#ccc', 201 }, 202 sensor: { 203 marginTop: 15, 204 paddingHorizontal: 10, 205 }, 206}); 207