1import { Accelerometer } from 'expo-sensors';
2import React from 'react';
3import { Animated, Dimensions, StyleSheet, Text, View, ActivityIndicator } from 'react-native';
4import { Colors } from '../constants';
5
6const COUNT = 5;
7const ITEM_SIZE = Dimensions.get('window').width / COUNT;
8
9interface State {
10  items: any[];
11  error: string | null;
12  isSetup: boolean;
13}
14
15interface Props {
16  numItems: number;
17  perspective: number;
18}
19
20export default class AccelerometerScreen extends React.Component<Props, State> {
21  static navigationOptions = {
22    title: 'Accelerometer',
23  };
24
25  static defaultProps: Props = {
26    perspective: 200,
27    numItems: COUNT,
28  };
29
30  readonly state: State;
31
32  constructor(props: Props) {
33    super(props);
34
35    const items = [];
36    for (let i = 0; i < this.props.numItems; i++) {
37      items.push({ position: new Animated.ValueXY() });
38    }
39    this.state = { items, error: null, isSetup: false };
40  }
41
42  componentWillUnmount() {
43    Accelerometer.removeAllListeners();
44  }
45
46  async componentDidMount() {
47    if (!(await Accelerometer.isAvailableAsync())) {
48      this.setState({
49        error:
50          'Cannot start demo!' +
51          '\nEnable device orientation in Settings > Safari > Motion & Orientation Access' +
52          '\nalso ensure that you are hosting with https as DeviceMotion is now a secure API on iOS Safari.',
53        isSetup: false,
54      });
55      return;
56    }
57
58    Accelerometer.addListener(({ x, y }) => {
59      this.state.items.forEach((_, index) => {
60        // All that matters is that the values are the same on iOS, Android, Web, ect...
61
62        const { perspective } = this.props;
63        const nIndex = index + 1;
64
65        Animated.spring(this.state.items[index].position, {
66          toValue: {
67            x: (Number(x.toFixed(1)) * perspective * nIndex) / COUNT,
68            y: (-y.toFixed(1) * perspective * nIndex) / COUNT,
69          },
70          friction: 7,
71        }).start();
72      });
73    });
74
75    this.setState({ isSetup: true });
76  }
77
78  render() {
79    const { error, items, isSetup } = this.state;
80
81    if (error) {
82      return (
83        <Container>
84          <Text style={[styles.text, { color: 'red' }]}>{error}</Text>
85        </Container>
86      );
87    }
88
89    if (!isSetup) {
90      return (
91        <Container>
92          <ActivityIndicator size="large" color={Colors.tintColor} />
93          <Text
94            style={[
95              styles.text,
96              {
97                marginTop: 16,
98              },
99            ]}>
100            Checking Permissions
101          </Text>
102        </Container>
103      );
104    }
105
106    return (
107      <Container>
108        <Text style={[styles.text, styles.message]}>
109          {`The stack should move against the orientation of the device.
110          If you lift the bottom of the phone up, the stack should translate down towards the bottom of the screen.
111          The balls all line up when the phone is in "display up" mode.`}
112        </Text>
113        {items.map((val, index) => {
114          return (
115            <Animated.View
116              key={`item-${index}`}
117              style={[
118                styles.ball,
119                {
120                  opacity: (index + 1) / COUNT,
121                  transform: [
122                    { translateX: items[index].position.x },
123                    { translateY: items[index].position.y },
124                  ],
125                },
126              ]}
127            />
128          );
129        })}
130      </Container>
131    );
132  }
133}
134
135const Container = (props: any) => <View {...props} style={styles.container} />;
136
137const styles = StyleSheet.create({
138  container: {
139    flex: 1,
140    alignItems: 'center',
141    justifyContent: 'center',
142  },
143  text: {
144    zIndex: 1,
145    fontWeight: '800',
146    color: Colors.tintColor,
147    textAlign: 'center',
148  },
149  message: {
150    position: 'absolute',
151    top: 24,
152    left: 24,
153    right: 24,
154  },
155  ball: {
156    position: 'absolute',
157    width: ITEM_SIZE,
158    height: ITEM_SIZE,
159    borderRadius: ITEM_SIZE,
160    backgroundColor: 'red',
161  },
162});
163