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