Skip to content

Commit 7a1e731

Browse files
authored
Devices Screen UIs (#60)
1 parent 914289d commit 7a1e731

27 files changed

+513
-129
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ buck-out/
6060

6161
# env
6262
.env
63+
*.ttf

src/api/LiveApi.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export default class LiveApi extends ApiInterface {
55
request = async (method, resource, body = {}) => {
66
try {
77
const url = `${this.endpoint}/${resource}`;
8-
const options = {method: method, body: JSON.stringify(body)};
8+
const _body = ['GET'].includes(method) ? null : JSON.stringify(body);
9+
const options = {method: method, body: _body};
910

1011
const response = await fetch(url, options);
1112

src/api/mock-data/verification.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
"id": 776655,
44
"site": 2
55
}
6-
}
6+
}

src/components/DeviceRowSimple.js

-60
This file was deleted.

src/components/devices/DeviceIcons.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @format
3+
* @flow strict-local
4+
*/
5+
import React from 'react';
6+
import {StyleSheet} from 'react-native';
7+
import {Colors, Typography, Spacing} from '../../styles';
8+
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
9+
10+
const DeviceIcons: () => React$Node = ({status}) => {
11+
const chooseIcon = icon => (
12+
<MaterialIcon key={icon} style={styles.icon} name={icon} />
13+
);
14+
15+
// TODO: check locally if the device is connected to BLE
16+
const isDeviceConnectedToBLE = uuid => true;
17+
// TODO: push a notification to the user
18+
const pushNotfication = message => true;
19+
20+
const icons = [];
21+
22+
if (status.features.ble) {
23+
const isBLEOn = isDeviceConnectedToBLE(status.features.ble.uuid);
24+
const isConnectedToBLE = isBLEOn ? 'bluetooth' : 'bluetooth-off';
25+
26+
// TODO: determine if usage is witin wear-time before pushing
27+
if (!isBLEOn) {
28+
pushNotfication('');
29+
}
30+
31+
icons.push(chooseIcon(isConnectedToBLE));
32+
}
33+
34+
if (status.connection.battery) {
35+
const percent = status.connection.battery;
36+
const batteryStatus = percent === 100 ? 'battery' : `battery-${percent}`;
37+
icons.push(chooseIcon(batteryStatus));
38+
}
39+
40+
if (status.features.wired) {
41+
const isWiredConnected = status.connection.wifi
42+
? 'power-plug-outline'
43+
: 'power-plug-off-outline';
44+
icons.push(chooseIcon(isWiredConnected));
45+
}
46+
47+
if (status.features.wifi) {
48+
const wifiIcon = status.connection.wifi ? 'wifi' : 'wifi-off';
49+
icons.push(chooseIcon(wifiIcon));
50+
}
51+
52+
return icons;
53+
};
54+
55+
const styles = StyleSheet.create({
56+
icon: {
57+
fontSize: Typography.FONT_SIZE_16,
58+
color: Colors.BLACK,
59+
padding: Spacing.SCALE_4,
60+
},
61+
});
62+
63+
export default DeviceIcons;

src/components/devices/DeviceList.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @format
3+
* @flow strict-local
4+
*/
5+
import React from 'react';
6+
import {useTranslation} from 'react-i18next';
7+
import {Button, RefreshControl} from 'react-native';
8+
import {Colors} from '../../styles';
9+
import {FlatList} from 'react-native-gesture-handler';
10+
11+
import DeviceRow from '../../components/devices/DeviceRow';
12+
import DeviceIcons from '../../components/devices/DeviceIcons';
13+
14+
import {FormatBytes, LastUploadTime} from '../../util';
15+
16+
const DeviceList: () => React$Node = ({devices, refreshing, onRefresh}) => {
17+
const {t} = useTranslation(['api', 'devices']);
18+
19+
const hasError = device => !!device.status.error;
20+
21+
const statusForManualTransfer = deviceID => {
22+
// Note: Axivity, eBedSensor, & McRoberts data transfer is at end
23+
return ['AX6', 'BED', 'MMM'].includes(deviceID)
24+
? t('devices:status.manualTransfer')
25+
: t('devices:status.noData');
26+
};
27+
28+
const setSyncStatus = device => {
29+
const {days, hours} = LastUploadTime(device.status.data.lastUploaded);
30+
const lastUploaded =
31+
days > 0
32+
? t('devices:status.daysAgo', {days})
33+
: t('devices:status.hoursAgo', {hours});
34+
const filesize = FormatBytes(device.status.data.size);
35+
36+
const status = !device.status.data.isOnDevice
37+
? statusForManualTransfer(device.id)
38+
: t('devices:status.sync', {
39+
filesize,
40+
lastUploaded,
41+
});
42+
43+
return hasError(device) ? t(`${device.status.error}.message`) : status;
44+
};
45+
46+
const renderDeviceIcons = device => {
47+
if (hasError(device)) {
48+
return (
49+
<Button
50+
title={t(`${device.status.error}.action`)}
51+
color={Colors.PRIMARY}
52+
// TODO: navigate to appropriate SupportDoc#Header
53+
// onPress={() => {}}
54+
/>
55+
);
56+
}
57+
58+
return <DeviceIcons key={device.id} status={device.status.hardware} />;
59+
};
60+
61+
const renderItem = ({item: device}) => (
62+
<DeviceRow
63+
key={device.id}
64+
name={device.name}
65+
image={device.image}
66+
status={setSyncStatus(device)}
67+
children={renderDeviceIcons(device)}
68+
isError={hasError(device)}
69+
/>
70+
);
71+
72+
const compareByError = (a, b) => !hasError(a) || hasError(b);
73+
74+
return (
75+
<FlatList
76+
alwaysBounceVertical={true}
77+
showsVerticalScrollIndicator={false}
78+
refreshControl={
79+
<RefreshControl
80+
enabled={true}
81+
colors={[Colors.PRIMARY]}
82+
refreshing={refreshing}
83+
onRefresh={onRefresh}
84+
/>
85+
}
86+
data={devices.sort(compareByError)}
87+
renderItem={renderItem}
88+
keyExtractor={device => device.name}
89+
/>
90+
);
91+
};
92+
93+
export default DeviceList;

src/components/devices/DeviceRow.js

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @format
3+
* @flow strict-local
4+
*/
5+
import React from 'react';
6+
import {StyleSheet, Text, Image, TouchableHighlight, View} from 'react-native';
7+
import {Colors, Typography, Spacing} from '../../styles';
8+
import FontAwesome from 'react-native-vector-icons/FontAwesome';
9+
10+
const DeviceRow: () => React$Node = ({
11+
image,
12+
name,
13+
status = '',
14+
isError = false,
15+
children = null,
16+
onPress = null,
17+
}) => {
18+
const rowStyle = isError ? [styles.row, styles.rowWithError] : styles.row;
19+
20+
const flexDirection = status.length > 0 ? 'column' : 'row';
21+
22+
return (
23+
<TouchableHighlight
24+
activeOpacity={0.85}
25+
underlayColor={Colors.BORDER}
26+
style={rowStyle}
27+
onPress={onPress}>
28+
<View style={styles.container}>
29+
<Image style={styles.image} source={image} resizeMode={'center'} />
30+
<View style={[styles.contentContainer, {flexDirection}]}>
31+
<View style={styles.content}>
32+
{isError && (
33+
<FontAwesome
34+
style={styles.error}
35+
size={Typography.FONT_SIZE_18}
36+
name="exclamation-circle"
37+
/>
38+
)}
39+
<Text style={[Typography.TITLE, styles.title]}>{name}</Text>
40+
</View>
41+
<Text style={[Typography.SUBTITLE, styles.subtitle]}>{status}</Text>
42+
</View>
43+
<View style={styles.actions}>{children}</View>
44+
</View>
45+
</TouchableHighlight>
46+
);
47+
};
48+
49+
const styles = StyleSheet.create({
50+
row: {
51+
borderWidth: 1,
52+
marginBottom: Spacing.SCALE_8,
53+
paddingRight: Spacing.SCALE_8,
54+
borderColor: Colors.BORDER,
55+
},
56+
rowWithError: {
57+
backgroundColor: Colors.SELECTED,
58+
borderColor: Colors.PRIMARY,
59+
},
60+
container: {
61+
flex: 1,
62+
flexDirection: 'row',
63+
alignItems: 'center',
64+
padding: Spacing.SCALE_8,
65+
},
66+
image: {
67+
width: Spacing.SCALE_42,
68+
height: Spacing.SCALE_42,
69+
},
70+
contentContainer: {
71+
flex: 1,
72+
alignItems: 'flex-start',
73+
},
74+
content: {
75+
flexDirection: 'row',
76+
marginLeft: Spacing.SCALE_16,
77+
justifyContent: 'center',
78+
alignItems: 'center',
79+
},
80+
title: {
81+
fontSize: Typography.FONT_SIZE_20,
82+
},
83+
subtitle: {
84+
marginLeft: Spacing.SCALE_16,
85+
fontSize: Typography.FONT_SIZE_12,
86+
},
87+
actions: {
88+
flexDirection: 'row',
89+
alignItems: 'flex-end',
90+
},
91+
error: {
92+
paddingRight: Spacing.SCALE_4,
93+
paddingTop: Spacing.SCALE_4,
94+
color: Colors.PRIMARY,
95+
},
96+
});
97+
98+
export default DeviceRow;
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @format
3+
* @flow strict-local
4+
*/
5+
import React from 'react';
6+
import {StyleSheet} from 'react-native';
7+
import {Colors, Spacing} from '../../styles';
8+
import FontAwesome from 'react-native-vector-icons/FontAwesome';
9+
10+
import DeviceRow from '../devices/DeviceRow';
11+
12+
const DeviceRowSupport: () => React$Node = props => {
13+
const children = <FontAwesome style={styles.icon} name="chevron-right" />;
14+
15+
return <DeviceRow {...props} children={children} />;
16+
};
17+
18+
const styles = StyleSheet.create({
19+
icon: {
20+
fontSize: Spacing.SCALE_16,
21+
color: Colors.BLACK,
22+
padding: Spacing.SCALE_4,
23+
},
24+
});
25+
26+
export default DeviceRowSupport;

0 commit comments

Comments
 (0)