1import { H2 } from '@expo/html-elements';
2import { Pedometer } from 'expo-sensors';
3import * as React from 'react';
4import { ScrollView, Text, View } from 'react-native';
5
6import ListButton from '../components/ListButton';
7import usePermissions from '../utilities/usePermissions';
8import { useResolvedValue } from '../utilities/useResolvedValue';
9
10function usePedometer({ isActive }: { isActive: boolean }): Pedometer.PedometerResult | null {
11  const [data, setData] = React.useState<Pedometer.PedometerResult | null>(null);
12  const listener = React.useRef<Pedometer.Subscription | null>(null);
13
14  React.useEffect(() => {
15    return () => {
16      listener.current?.remove();
17    };
18  }, []);
19
20  React.useEffect(() => {
21    if (isActive) {
22      listener.current = Pedometer.watchStepCount(setData);
23    } else {
24      listener.current?.remove();
25    }
26  }, [isActive]);
27
28  return data;
29}
30
31function usePedometerHistory({
32  start,
33  end,
34}: {
35  start: Date;
36  end: Date;
37}): Pedometer.PedometerResult | null {
38  const [data, setData] = React.useState<Pedometer.PedometerResult | null>(null);
39  const isMounted = React.useRef(true);
40
41  React.useEffect(() => {
42    return () => {
43      isMounted.current = false;
44    };
45  }, []);
46
47  React.useEffect(() => {
48    Pedometer.getStepCountAsync(start, end).then((data) => {
49      if (isMounted.current) {
50        setData(data);
51      }
52    });
53  }, [start, end]);
54
55  return data;
56}
57
58function StepTrackerView() {
59  const [isActive, setActive] = React.useState(false);
60  const data = usePedometer({ isActive });
61  const message = data?.steps ? `Total steps ${data.steps}` : `Waiting...`;
62  return (
63    <View style={{ padding: 10 }}>
64      <H2>Step Tracker</H2>
65      <ListButton onPress={() => setActive(true)} disabled={isActive} title="Start" />
66      <ListButton onPress={() => setActive(false)} disabled={!isActive} title="Stop" />
67      <Text style={{ paddingTop: 10, fontWeight: 'bold' }}>{message}</Text>
68    </View>
69  );
70}
71
72function StepHistoryMessage() {
73  const today = React.useMemo(() => {
74    return new Date();
75  }, []);
76  const yesterday = React.useMemo(() => {
77    const yesterday = new Date();
78    yesterday.setDate(today.getDate() - 1);
79    return yesterday;
80  }, [today]);
81
82  const data = usePedometerHistory({ start: yesterday, end: today });
83
84  const message = data ? `Steps in the last day: ${data.steps}` : `Loading health data...`;
85  return (
86    <View style={{ padding: 10 }}>
87      <H2>Step History</H2>
88      <Text>{message}</Text>
89    </View>
90  );
91}
92
93export default function PedometerGuard() {
94  const [isPermissionsGranted] = usePermissions(Pedometer.requestPermissionsAsync);
95  const [isAvailable] = useResolvedValue(Pedometer.isAvailableAsync);
96
97  if (isAvailable === null) {
98    return null;
99  }
100
101  if (!isPermissionsGranted || !isAvailable) {
102    // this can also occur if the device doesn't have a pedometer
103    const message = isAvailable
104      ? 'You have not granted permission to use the device motion on this device!'
105      : 'Your device does not have a pedometer';
106    return (
107      <View style={{ flex: 1, justifyContent: 'center', padding: 24, alignItems: 'center' }}>
108        <Text>{message}</Text>
109      </View>
110    );
111  }
112  return <PedometerScreen />;
113}
114
115function PedometerScreen() {
116  return (
117    <ScrollView>
118      <StepTrackerView />
119      <StepHistoryMessage />
120    </ScrollView>
121  );
122}
123
124PedometerGuard.navigationOptions = {
125  title: 'Pedometer',
126};
127