@eohjsc/react-native-smart-city 0.3.1 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +4 -2
  2. package/react-native-smart-city.podspec +1 -0
  3. package/src/commons/ActionGroup/ColorPickerTemplate.js +36 -23
  4. package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplate.js +8 -2
  5. package/src/commons/ActionGroup/SliderRangeTemplate.js +5 -1
  6. package/src/commons/ConnectingProcess/DeviceItem/DeviceItem.js +7 -3
  7. package/src/commons/ConnectingProcess/DeviceItem/DeviceItemStyles.js +8 -11
  8. package/src/commons/ConnectingProcess/__test__/DeviceItem.test.js +3 -2
  9. package/src/commons/ConnectingProcess/index.js +64 -18
  10. package/src/commons/Device/ItemDevice.js +4 -1
  11. package/src/commons/Device/WaterQualitySensor/QualityIndicatorsItem.js +7 -2
  12. package/src/commons/Form/CurrencyInput.js +15 -1
  13. package/src/commons/Form/TextInputPassword.js +1 -1
  14. package/src/commons/HeaderAni/index.js +6 -1
  15. package/src/commons/MediaPlayerDetail/index.js +16 -7
  16. package/src/commons/Sharing/MemberList.js +10 -2
  17. package/src/commons/Sharing/WrapHeaderScrollable.js +2 -0
  18. package/src/commons/SubUnit/ShortDetail.js +24 -7
  19. package/src/commons/SubUnit/__test__/ShortDetail.test.js +9 -2
  20. package/src/configs/API.js +4 -0
  21. package/src/configs/Constants.js +19 -0
  22. package/src/configs/SCConfig.js +2 -0
  23. package/src/hooks/Common/useSensorsStatus.js +11 -8
  24. package/src/hooks/useReceiveNotifications.js +1 -1
  25. package/src/navigations/UnitStack.js +3 -20
  26. package/src/navigations/UnitStackStyles.js +21 -0
  27. package/src/screens/AddNewDevice/__test__/AddNewDevice.test.js +8 -1
  28. package/src/screens/AddNewDevice/__test__/ConnectingDevices.test.js +1 -1
  29. package/src/screens/Device/EditDevice/index.js +15 -13
  30. package/src/screens/SubUnit/AddSubUnit.js +23 -17
  31. package/src/screens/SubUnit/EditSubUnit.js +15 -13
  32. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +9 -23
  33. package/src/screens/SyncLGDevice/__test__/AddLGDevice.test.js +8 -1
  34. package/src/screens/Unit/SelectAddress.js +7 -1
  35. package/src/screens/Unit/Station/index.js +3 -0
  36. package/src/screens/Unit/__test__/SelectAddress.test.js +80 -3
  37. package/src/screens/Unit/components/MyUnitDevice/index.js +4 -4
  38. package/src/screens/Unit/components/__test__/MyUnitDevice.test.js +2 -2
  39. package/src/screens/UnitSummary/components/WaterQuality/Item/index.js +10 -2
  40. package/src/utils/Apis/axios.js +9 -1
  41. package/src/utils/I18n/translations/en.json +5 -1
  42. package/src/utils/I18n/translations/vi.json +5 -1
  43. package/src/utils/Permission/common.js +29 -1
  44. package/src/utils/Setting/Location.js +30 -0
  45. package/src/utils/__test__/Utils.test.js +12 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.3.01",
4
+ "version": "0.3.04",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -100,7 +100,7 @@
100
100
  "@ant-design/icons-react-native": "^2.2.1",
101
101
  "@ant-design/react-native": "^4.0.5",
102
102
  "@babel/helper-environment-visitor": "^7.16.7",
103
- "@eohjsc/highcharts": "^1.0.8",
103
+ "@eohjsc/highcharts": "^1.0.9",
104
104
  "@eohjsc/react-native-keyboard-aware-scroll-view": "^0.9.5",
105
105
  "@formatjs/intl-getcanonicallocales": "^1.4.5",
106
106
  "@formatjs/intl-numberformat": "^5.6.2",
@@ -146,6 +146,8 @@
146
146
  "react-hooks-global-state": "^1.0.1",
147
147
  "react-i18next": "^11.8.12",
148
148
  "react-native-alert-async": "^1.0.5",
149
+ "react-native-android-keyboard-adjust": "^1.2.0",
150
+ "react-native-android-location-enabler": "^1.2.2",
149
151
  "react-native-android-wifi": "^0.0.41",
150
152
  "react-native-appearance": "^0.3.4",
151
153
  "react-native-base64": "^0.1.0",
@@ -22,6 +22,7 @@ Pod::Spec.new do |s|
22
22
  s.requires_arc = true
23
23
 
24
24
  s.dependency "React"
25
+ s.dependency "RNPermissions"
25
26
  # ...
26
27
  # s.dependency "..."
27
28
  end
@@ -1,38 +1,51 @@
1
- import React, { memo, useCallback, useState, useEffect, useMemo } from 'react';
1
+ import React, { memo, useState, useEffect } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import styles from './ColorPickerTemplateStyles';
4
4
  import ColorPicker from 'react-native-wheel-color-picker';
5
5
  import { watchMultiConfigs } from '../../iot/Monitor';
6
6
  import { useConfigGlobalState } from '../../iot/states';
7
7
 
8
+ let isFirstTime = true;
9
+
10
+ const WheelColorPicker = ({ valueColor, onChangeColor }) => {
11
+ return (
12
+ <ColorPicker
13
+ style={styles.colorPicker}
14
+ sliderHidden={true}
15
+ swatches={false}
16
+ color={valueColor}
17
+ onColorChangeComplete={onChangeColor}
18
+ thumbSize={16}
19
+ />
20
+ );
21
+ };
22
+
8
23
  const ColorPickerTemplate = memo(({ actionGroup, doAction, sensor }) => {
9
24
  const { configuration } = actionGroup;
10
25
  const [valueColorComplete, setValueColorComplete] = useState('');
11
- const [isFirstColor, setIsFirstColor] = useState(false);
12
26
  const [configValues] = useConfigGlobalState('configValues');
13
27
 
14
- const onChangeColor = useCallback(
15
- (color) => {
16
- if (!isFirstColor) {
17
- setIsFirstColor(true);
18
- return;
19
- }
28
+ const onChangeColor = (color) => {
29
+ const to = setTimeout(() => {
30
+ isFirstTime = false;
31
+ clearTimeout(to);
32
+ }, 1000);
33
+
34
+ !isFirstTime &&
20
35
  doAction(
21
36
  configuration?.action_color_data,
22
37
  JSON.stringify({ value: color })
23
38
  );
24
- },
25
- [configuration?.action_color_data, doAction, isFirstColor]
26
- );
27
- const valueColor = useMemo(() => {
28
- return valueColorComplete || '';
29
- }, [valueColorComplete]);
39
+ };
30
40
 
31
41
  useEffect(() => {
32
42
  const { config } = configuration;
33
43
  const configValue = configValues[config];
34
- setValueColorComplete(configValue);
35
- }, [configuration.config, configValues, configuration]);
44
+ if (configValue && isFirstTime) {
45
+ setValueColorComplete(`#${configValue?.toString(16)}`);
46
+ }
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [configValues]);
36
49
 
37
50
  useEffect(() => {
38
51
  if (sensor?.is_managed_by_backend && sensor.device_type !== 'GOOGLE_HOME') {
@@ -40,15 +53,15 @@ const ColorPickerTemplate = memo(({ actionGroup, doAction, sensor }) => {
40
53
  }
41
54
  }, [sensor, configuration.config]);
42
55
 
56
+ useEffect(() => {
57
+ return () => (isFirstTime = true);
58
+ }, []);
59
+
43
60
  return (
44
61
  <View style={styles.viewPickColor}>
45
- <ColorPicker
46
- style={styles.colorPicker}
47
- sliderHidden={true}
48
- swatches={false}
49
- color={valueColor}
50
- onColorChangeComplete={onChangeColor}
51
- thumbSize={16}
62
+ <WheelColorPicker
63
+ valueColor={valueColorComplete}
64
+ onChangeColor={onChangeColor}
52
65
  />
53
66
  </View>
54
67
  );
@@ -5,15 +5,20 @@ import React, { memo } from 'react';
5
5
  import { TouchableOpacity, View } from 'react-native';
6
6
  import { Colors } from '../../../configs';
7
7
  import styles from './OnOffButtonTemplateStyle';
8
+ import { TESTID } from '../../../configs/Constants';
8
9
 
9
10
  const OnOffButtonTemplate = memo(
10
11
  ({ isOn, triggerAction, actionGroup, isLight = false }) => {
11
- const { configuration } = actionGroup;
12
+ const { configuration, id } = actionGroup;
12
13
 
13
14
  return (
14
15
  <>
15
16
  <View style={styles.barrierControlContainer}>
16
- <TouchableOpacity style={styles.bigCircle} onPress={triggerAction}>
17
+ <TouchableOpacity
18
+ style={styles.bigCircle}
19
+ onPress={triggerAction}
20
+ testID={`${TESTID.ON_OFF_BUTTON}-${id}`}
21
+ >
17
22
  <View style={styles.smallCircle}>
18
23
  <Icon
19
24
  name={isOn ? configuration.icon_on : configuration.icon_off}
@@ -25,6 +30,7 @@ const OnOffButtonTemplate = memo(
25
30
  styles.textBig,
26
31
  { color: isOn ? Colors.Gray8 : Colors.Gray6 },
27
32
  ]}
33
+ testID={`${TESTID.SENSOR_STATUS}-${id}`}
28
34
  >
29
35
  {isOn ? configuration.text_on : configuration.text_off}
30
36
  </Text>
@@ -33,7 +33,11 @@ const SliderRangeTemplate = memo(({ actionGroup, doAction, sensor }) => {
33
33
  useEffect(() => {
34
34
  const { config } = configuration;
35
35
  const configValue = configValues[config];
36
- setValueBrightness(configValue);
36
+ let valueTemp = configValue;
37
+ if (configValue > 0) {
38
+ valueTemp = Math.round((configValue / 254) * 100);
39
+ }
40
+ setValueBrightness(valueTemp);
37
41
  }, [configuration.config, configValues, configuration]);
38
42
 
39
43
  useEffect(() => {
@@ -2,13 +2,17 @@ import React, { memo } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import styles from './DeviceItemStyles';
4
4
  import FImage from '../../FImage';
5
- import Text from '../../Text';
5
+ import _TextInput from '../../Form/TextInput';
6
6
 
7
- const DeviceItem = memo(({ icon, name }) => {
7
+ const DeviceItem = memo(({ icon, name, setNewName }) => {
8
8
  return (
9
9
  <View style={styles.container}>
10
10
  {!!icon && <FImage source={{ uri: icon }} style={styles.iconSensor} />}
11
- <Text style={styles.textItem}>{name}</Text>
11
+ <_TextInput
12
+ value={name}
13
+ textInputStyle={styles.textItem}
14
+ onChange={setNewName}
15
+ />
12
16
  </View>
13
17
  );
14
18
  });
@@ -1,23 +1,14 @@
1
1
  import { StyleSheet } from 'react-native';
2
- import { Constants, Colors } from '../../../configs';
3
-
4
- const marginItem = 12;
5
- const marginHorizontal = 5;
6
- const widthItem = (Constants.width - marginHorizontal * 2 - marginItem) / 2;
7
- const heightItem = (widthItem / 166) * 60;
2
+ import { Colors } from '../../../configs';
8
3
 
9
4
  export default StyleSheet.create({
10
5
  container: {
11
- paddingHorizontal: 15,
12
6
  borderRadius: 10,
13
7
  width: 250,
14
- height: heightItem,
15
8
  borderWidth: 1,
16
9
  borderColor: Colors.Gray4,
17
10
  backgroundColor: Colors.White,
18
11
  justifyContent: 'center',
19
- alignItems: 'center',
20
- marginBottom: 16,
21
12
  flexDirection: 'row',
22
13
  shadowColor: Colors.Shadow,
23
14
  shadowOffset: {
@@ -36,7 +27,13 @@ export default StyleSheet.create({
36
27
  resizeMode: 'contain',
37
28
  },
38
29
  textItem: {
39
- marginLeft: 15,
30
+ borderWidth: 0,
31
+ borderBottomWidth: 1,
32
+ borderBottomColor: Colors.Primary,
33
+ textAlign: 'center',
34
+ marginTop: -10,
35
+ padding: 5,
40
36
  fontSize: 16,
37
+ height: 50,
41
38
  },
42
39
  });
@@ -2,7 +2,8 @@ import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
3
  import DeviceItem from '../DeviceItem/DeviceItem';
4
4
  import FImage from '../../FImage';
5
- import Text from '../../Text';
5
+ import _TextInput from '../../Form/TextInput';
6
+
6
7
  describe('Test DeviceItem button', () => {
7
8
  let tree;
8
9
  test('create DeviceItem button', () => {
@@ -11,7 +12,7 @@ describe('Test DeviceItem button', () => {
11
12
  });
12
13
  const instance = tree.root;
13
14
  const image = instance.findAllByType(FImage);
14
- const text = instance.findAllByType(Text);
15
+ const text = instance.findAllByType(_TextInput);
15
16
  expect(image).toHaveLength(0);
16
17
  expect(text).toHaveLength(1);
17
18
  });
@@ -6,7 +6,7 @@ import { useNavigation } from '@react-navigation/native';
6
6
  import ImageSuccessfully from '../../Images/Common/SuccessfullyConnected.svg';
7
7
 
8
8
  import Text from '../Text';
9
- import { axiosPost } from '../../utils/Apis/axios';
9
+ import { axiosPatch, axiosPost } from '../../utils/Apis/axios';
10
10
  import { API, Colors } from '../../configs';
11
11
  import styles from './styles';
12
12
  import DeviceItem from './DeviceItem/DeviceItem';
@@ -32,25 +32,27 @@ const ConnectingProcess = ({ route }) => {
32
32
  const [isLoading, setIsLoading] = useState(true);
33
33
  const [sensor, setSensor] = useState(null);
34
34
  const user = useSCContextSelector((state) => state?.auth?.account?.user);
35
+ const [newName, setNewName] = useState('');
35
36
 
36
37
  const ConnectingDevice = useCallback(async () => {
37
38
  setIsLoading(true);
38
39
  switch (devicePrefixName) {
39
- case 'SENSOR':
40
- {
41
- const body = { imei: scan_sensor_data?.imei, chip: gateway?.id };
42
- const { success, data } = await axiosPost(
43
- API.SUB_UNIT.SENSOR_SCAN(station.id),
44
- body
45
- );
46
- if (success) {
47
- setSensor(data);
48
- } else {
49
- ToastBottomHelper.error(JSON.stringify(data));
50
- goBack();
51
- }
40
+ case 'SENSOR': {
41
+ const body = { imei: scan_sensor_data?.imei, chip: gateway?.id };
42
+ const { success, data } = await axiosPost(
43
+ API.SUB_UNIT.SENSOR_SCAN(station.id),
44
+ body
45
+ );
46
+ if (success) {
47
+ setSensor(data);
48
+ setNewName(data?.name);
49
+ } else {
50
+ ToastBottomHelper.error(JSON.stringify(data));
51
+ goBack();
52
52
  }
53
+
53
54
  break;
55
+ }
54
56
  case 'ROBOT': {
55
57
  const { success, data } = await axiosPost(API.UNIT.CHIP_SCAN(unit.id), {
56
58
  imei: gateway?.imei,
@@ -63,6 +65,7 @@ const ConnectingProcess = ({ route }) => {
63
65
  });
64
66
  if (success) {
65
67
  setSensor(data);
68
+ setNewName(data?.name);
66
69
  } else {
67
70
  ToastBottomHelper.error(JSON.stringify(data));
68
71
  goBack();
@@ -79,6 +82,7 @@ const ConnectingProcess = ({ route }) => {
79
82
  );
80
83
  if (success) {
81
84
  setSensor({ name: gateway?.model });
85
+ setNewName(gateway?.model);
82
86
  } else {
83
87
  ToastBottomHelper.error(JSON.stringify(data));
84
88
  goBack();
@@ -114,12 +118,46 @@ const ConnectingProcess = ({ route }) => {
114
118
  station?.name !== undefined ? '- ' + station?.name : ''
115
119
  }`}
116
120
  </Text>
117
- <DeviceItem icon={sensor?.icon_kit} name={sensor?.name} />
121
+ <DeviceItem
122
+ icon={sensor?.icon_kit}
123
+ name={newName}
124
+ setNewName={setNewName}
125
+ />
118
126
  </View>
119
127
  );
120
- }, [sensor?.icon_kit, sensor?.name, station?.name, t, unit?.name, unit_name]);
128
+ }, [newName, sensor?.icon_kit, station?.name, t, unit?.name, unit_name]);
129
+
130
+ const handleDone = useCallback(async () => {
131
+ let result, message;
132
+ switch (devicePrefixName) {
133
+ case 'SENSOR': {
134
+ const { success, data } = await axiosPatch(
135
+ API.SENSOR.SENSOR_DETAIL(sensor?.id),
136
+ {
137
+ name: newName,
138
+ }
139
+ );
140
+ result = success;
141
+ message = data;
142
+ break;
143
+ }
144
+ case 'ROBOT':
145
+ case 'LITE': {
146
+ const { success, data } = await axiosPatch(
147
+ API.CHIP.CHIP_DETAIL(sensor?.id),
148
+ {
149
+ name: newName,
150
+ }
151
+ );
152
+ result = success;
153
+ message = data;
154
+ break;
155
+ }
156
+ }
121
157
 
122
- const handleDone = useCallback(() => {
158
+ if (!result) {
159
+ ToastBottomHelper.error(JSON.stringify(message));
160
+ }
123
161
  navigate(Routes.UnitStack, {
124
162
  screen: Routes.UnitDetail,
125
163
  params: {
@@ -129,7 +167,15 @@ const ConnectingProcess = ({ route }) => {
129
167
  stationId: station?.id,
130
168
  },
131
169
  });
132
- }, [navigate, station?.id, unit, unit_id]);
170
+ }, [
171
+ devicePrefixName,
172
+ navigate,
173
+ newName,
174
+ sensor?.id,
175
+ station?.id,
176
+ unit,
177
+ unit_id,
178
+ ]);
133
179
 
134
180
  useEffect(() => {
135
181
  ConnectingDevice();
@@ -72,7 +72,10 @@ const ItemDevice = memo(
72
72
  const textConnected = isConnected ? t('connected') : t('disconnected');
73
73
 
74
74
  return (
75
- <TouchableWithoutFeedback onPress={goToSensorDisplay}>
75
+ <TouchableWithoutFeedback
76
+ onPress={goToSensorDisplay}
77
+ testID={`${TESTID.SENSOR_NAME}-${sensor?.id}`}
78
+ >
76
79
  <View
77
80
  style={[styles.container, wrapStyle, { borderColor }]}
78
81
  testID={TESTID.SUB_UNIT_DEVICES}
@@ -37,7 +37,7 @@ const QualityIndicatorItem = memo(
37
37
  >
38
38
  <IconOutline
39
39
  name={'info-circle'}
40
- size={16}
40
+ size={18}
41
41
  color={Colors.Gray8}
42
42
  />
43
43
  </TouchableOpacity>
@@ -92,6 +92,11 @@ const styles = StyleSheet.create({
92
92
  marginTop: 8,
93
93
  },
94
94
  iconInfo: {
95
- top: 10,
95
+ width: 40,
96
+ height: 40,
97
+ justifyContent: 'center',
98
+ alignItems: 'center',
99
+ marginRight: -10,
100
+ marginTop: -2,
96
101
  },
97
102
  });
@@ -1,4 +1,10 @@
1
- import React, { useState, useRef, useCallback, useMemo } from 'react';
1
+ import React, {
2
+ useState,
3
+ useRef,
4
+ useCallback,
5
+ useMemo,
6
+ useEffect,
7
+ } from 'react';
2
8
  import {
3
9
  View,
4
10
  TextInput,
@@ -6,6 +12,7 @@ import {
6
12
  TouchableWithoutFeedback,
7
13
  Platform,
8
14
  } from 'react-native';
15
+ import AndroidKeyboardAdjust from 'react-native-android-keyboard-adjust';
9
16
  import Text from '../Text';
10
17
  import { Colors } from '../../configs';
11
18
 
@@ -125,6 +132,13 @@ const CurrencyInput = ({
125
132
  return textInputValue.toString().length < 2 ? -12 : 0;
126
133
  }, [textInputValue]);
127
134
 
135
+ useEffect(() => {
136
+ const isAndroid = Platform.OS === 'android';
137
+ isAndroid && AndroidKeyboardAdjust.setAdjustResize();
138
+
139
+ return () => isAndroid && AndroidKeyboardAdjust.setAdjustPan();
140
+ }, []);
141
+
128
142
  return (
129
143
  <TouchableWithoutFeedback onPress={focusInput}>
130
144
  <View style={styles.wrap}>
@@ -90,7 +90,7 @@ const styles = StyleSheet.create({
90
90
  borderRadius: 2,
91
91
  paddingTop: 12,
92
92
  paddingBottom: 12,
93
- paddingHorizontal: 20,
93
+ paddingRight: 20,
94
94
  backgroundColor: Colors.White,
95
95
  // ...Theme.shadow,
96
96
  shadowColor: Colors.Gray13,
@@ -6,6 +6,7 @@ import { getStatusBarHeight } from 'react-native-iphone-x-helper';
6
6
 
7
7
  import Text from '../../commons/Text';
8
8
  import { Colors, Constants } from '../../configs';
9
+ import { TESTID } from '../../configs/Constants';
9
10
 
10
11
  const screenHeight = Constants.height;
11
12
  const default_height = 44;
@@ -89,7 +90,11 @@ const HeaderAni = memo(
89
90
  },
90
91
  ]}
91
92
  >
92
- <TouchableOpacity style={styles.btnBack} onPress={onPressLeft}>
93
+ <TouchableOpacity
94
+ style={styles.btnBack}
95
+ onPress={onPressLeft}
96
+ testID={TESTID.ICON_BACK}
97
+ >
93
98
  <Icon name={'left'} size={27} color={Colors.Gray9} />
94
99
  </TouchableOpacity>
95
100
  <View styles={styles.wrapRightComponent}>{rightComponent}</View>
@@ -6,7 +6,14 @@ import React, {
6
6
  useContext,
7
7
  useMemo,
8
8
  } from 'react';
9
- import { Image, View, StyleSheet, Text, TouchableOpacity } from 'react-native';
9
+ import {
10
+ Image,
11
+ View,
12
+ StyleSheet,
13
+ Text,
14
+ TouchableOpacity,
15
+ Platform,
16
+ } from 'react-native';
10
17
  import { VLCPlayer } from 'react-native-vlc-media-player';
11
18
  import { useTranslations } from '../../hooks/Common/useTranslations';
12
19
 
@@ -68,7 +75,9 @@ const MediaPlayerDetail = ({
68
75
  newHeight = 224;
69
76
  break;
70
77
  case 4:
71
- newWidth = Constants.width / 2;
78
+ newWidth =
79
+ Constants.width /
80
+ (Platform.OS === 'ios' && Constants.width < 400 ? 1.5 : 2);
72
81
  newHeight = 112;
73
82
  break;
74
83
  case 6:
@@ -91,9 +100,9 @@ const MediaPlayerDetail = ({
91
100
  <VLCPlayer
92
101
  autoAspectRatio={true}
93
102
  videoAspectRatio={
94
- width && height
95
- ? `${width}:${height}`
96
- : `${getWidthHeight().width}:${getWidthHeight().height}`
103
+ amount
104
+ ? `${getWidthHeight().width}:${getWidthHeight().height}`
105
+ : `${width}:${height}`
97
106
  }
98
107
  source={{
99
108
  initType: 2,
@@ -114,8 +123,8 @@ const MediaPlayerDetail = ({
114
123
  styles.player,
115
124
  style,
116
125
  {
117
- width: width || getWidthHeight().width,
118
- height: height || getWidthHeight().height,
126
+ width: amount ? getWidthHeight().width : width,
127
+ height: amount ? getWidthHeight().height : height,
119
128
  },
120
129
  ]}
121
130
  isLive={true}
@@ -1,7 +1,7 @@
1
1
  import React, { memo } from 'react';
2
2
  import { View, StyleSheet } from 'react-native';
3
3
  import { useTranslations } from '../../hooks/Common/useTranslations';
4
- import { Colors } from '../../configs';
4
+ import { Colors, Constants } from '../../configs';
5
5
  import Text from '../../commons/Text';
6
6
  import RowMember from './RowMember';
7
7
 
@@ -26,7 +26,9 @@ const MemberList = ({
26
26
  />
27
27
  ))}
28
28
  {!dataMember.length && (
29
- <Text style={styles.textCenter}>{t('no_member')}</Text>
29
+ <View style={styles.viewEmpty}>
30
+ <Text style={styles.textCenter}>{t('no_member')}</Text>
31
+ </View>
30
32
  )}
31
33
  </View>
32
34
  );
@@ -37,4 +39,10 @@ const styles = StyleSheet.create({
37
39
  box: {
38
40
  backgroundColor: Colors.White,
39
41
  },
42
+ viewEmpty: {
43
+ justifyContent: 'center',
44
+ alignItems: 'center',
45
+ height: Constants.height - 200,
46
+ backgroundColor: Colors.White,
47
+ },
40
48
  });
@@ -13,6 +13,7 @@ import { isIphoneX } from 'react-native-iphone-x-helper';
13
13
  import { Colors, Theme } from '../../configs';
14
14
  import HeaderAni, { heightHeader } from '../../commons/HeaderAni';
15
15
  import Text from '../../commons/Text';
16
+ import { TESTID } from '../../configs/Constants';
16
17
 
17
18
  const WrapHeaderScrollable = ({
18
19
  children,
@@ -59,6 +60,7 @@ const WrapHeaderScrollable = ({
59
60
  onLeft={onGoBack}
60
61
  />
61
62
  <Animated.ScrollView
63
+ testID={TESTID.ANIMATED_SCROLL}
62
64
  scrollEventThrottle={16}
63
65
  onScroll={Animated.event(
64
66
  [{ nativeEvent: { contentOffset: { y: animatedScrollYValue } } }],
@@ -15,6 +15,12 @@ import { standardizeCameraScreenSize } from '../../utils/Utils';
15
15
  import Routes from '../../utils/Route';
16
16
  import FastImage from 'react-native-fast-image';
17
17
  import MediaPlayerDetail from '../MediaPlayerDetail';
18
+ import {
19
+ keyPermission,
20
+ OpenSetting,
21
+ permitPermissionFunction,
22
+ } from '../../utils/Permission/common';
23
+ import { RESULTS } from 'react-native-permissions';
18
24
 
19
25
  const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
20
26
  Device.screenWidth - 32
@@ -71,13 +77,24 @@ const ShortDetailSubUnit = ({ unit, station }) => {
71
77
  };
72
78
 
73
79
  const handleOnAddNew = () => {
74
- navigate(Routes.AddDeviceStack, {
75
- screen: Routes.ScanSensorQR,
76
- params: {
77
- station_id: station?.id,
78
- unit_id: unit.id,
79
- unit_name: unit.name,
80
- },
80
+ permitPermissionFunction(keyPermission.CAMERA, (res) => {
81
+ if (res === RESULTS.GRANTED) {
82
+ navigate(Routes.AddDeviceStack, {
83
+ screen: Routes.ScanSensorQR,
84
+ params: {
85
+ station_id: station?.id,
86
+ unit_id: unit.id,
87
+ unit_name: unit.name,
88
+ },
89
+ });
90
+ return;
91
+ }
92
+ if (res === RESULTS.BLOCKED) {
93
+ OpenSetting(
94
+ t('camera_request_permission'),
95
+ t('camera_request_permission_des')
96
+ );
97
+ }
81
98
  });
82
99
  };
83
100
 
@@ -1,12 +1,14 @@
1
1
  import React from 'react';
2
2
  import { Image, View } from 'react-native';
3
3
  import { act, create } from 'react-test-renderer';
4
+ import mock from 'react-native-permissions/mock';
4
5
  import { TESTID } from '../../../configs/Constants';
5
6
  import { SCProvider } from '../../../context';
6
7
  import { mockSCStore } from '../../../context/mockStore';
7
8
  import ShortDetailSubUnit from '../ShortDetail';
8
9
  import ItemAddNew from '../../Device/ItemAddNew';
9
10
  import Routes from '../../../utils/Route';
11
+ import { keyPermission } from '../../../utils/Permission/common';
10
12
 
11
13
  const wrapComponent = (props) => (
12
14
  <SCProvider initState={mockSCStore({})}>
@@ -21,6 +23,10 @@ jest.mock('react', () => {
21
23
  };
22
24
  });
23
25
 
26
+ jest.mock('react-native-permissions', () => {
27
+ return mock;
28
+ });
29
+
24
30
  const mockedNavigate = jest.fn();
25
31
  jest.mock('@react-navigation/native', () => {
26
32
  return {
@@ -145,10 +151,10 @@ describe('test ShortDetail Subunit', () => {
145
151
  (item) =>
146
152
  item.props.testID === TESTID.SUB_UNIT_DEVICES && item.type === View
147
153
  );
148
- expect(itemDevice.length).toBe(1);
154
+ expect(itemDevice.length).toBe(0);
149
155
  });
150
156
 
151
- test('render ShortDetail add new device', () => {
157
+ test('render ShortDetail add new device', async () => {
152
158
  act(() => {
153
159
  tree = create(wrapComponent(props));
154
160
  });
@@ -157,6 +163,7 @@ describe('test ShortDetail Subunit', () => {
157
163
  act(() => {
158
164
  buttonAddNew.props.onAddNew();
159
165
  });
166
+ await mock.request(keyPermission.CAMERA);
160
167
  expect(mockedNavigate).toHaveBeenCalledWith(Routes.AddDeviceStack, {
161
168
  screen: Routes.ScanSensorQR,
162
169
  params: {