@eohjsc/react-native-smart-city 0.7.25 → 0.7.27
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.
- package/package.json +1 -1
- package/src/Images/Common/search-menu.svg +7 -0
- package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplateStyle.js +2 -1
- package/src/commons/ActionGroup/OneBigButtonTemplateStyle.js +1 -2
- package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -3
- package/src/commons/ActionGroup/TerminalBoxTemplate.js +1 -4
- package/src/commons/ActionGroup/TextBoxTemplate.js +1 -5
- package/src/commons/ActionGroup/ThreeButtonTemplate/components/ThreeButtonDefaultStyles.js +1 -1
- package/src/commons/ActionGroup/__test__/index.test.js +51 -0
- package/src/commons/ActionGroup/index.js +4 -0
- package/src/commons/Dashboard/MyPinnedSharedUnit/index.js +0 -10
- package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +114 -48
- package/src/commons/Dashboard/MyUnit/index.js +74 -27
- package/src/commons/Dashboard/MyUnit/styles.js +16 -1
- package/src/commons/DateTimeRangeChange/index.js +1 -1
- package/src/commons/SelectUnit/index.js +19 -5
- package/src/commons/SelectUnit/styles.js +0 -1
- package/src/commons/Widgets/IFrame/IFrameStyles.js +1 -0
- package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfigStyles.js +1 -0
- package/src/configs/API.js +4 -0
- package/src/configs/Constants.js +3 -0
- package/src/context/reducer.ts +0 -5
- package/src/navigations/UnitStack.js +8 -0
- package/src/screens/ActivityLog/FilterPopup.js +4 -79
- package/src/screens/ActivityLog/__test__/FilterPopup.test.js +2 -6
- package/src/screens/ActivityLog/__test__/index.test.js +51 -29
- package/src/screens/ActivityLog/index.js +0 -1
- package/src/screens/ActivityLog/styles/filterPopupStyles.js +5 -2
- package/src/screens/Device/__test__/sensorDisplayItem.test.js +27 -0
- package/src/screens/Device/components/DonutChart.js +5 -14
- package/src/screens/Device/components/SensorDisplayItem.js +107 -74
- package/src/screens/Device/components/VisualChart.js +0 -12
- package/src/screens/Device/styles.js +11 -0
- package/src/screens/SubUnit/AddSubUnit.js +18 -8
- package/src/screens/SubUnit/__test__/AddSubUnit.test.js +5 -3
- package/src/screens/Unit/GoToDetailUnit.js +30 -0
- package/src/screens/Unit/__test__/GoToDetailUnit.test.js +103 -0
- package/src/utils/I18n/translations/en.js +1 -0
- package/src/utils/I18n/translations/vi.js +1 -0
- package/src/utils/Route/index.js +1 -0
- package/src/utils/Storage.js +0 -2
- package/src/utils/Utils.js +2 -1
- package/src/utils/Functions/preloadImages.js +0 -38
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
|
5
|
+
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
|
|
6
|
+
<g><g><path fill="#000000" d="M14.4,58.3L14.4,58.3h96.3h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4c0,2.3-1.6,4.1-3.7,4.4v0H111l-0.4,0l-0.4,0H14.7l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,60.3,12,58.3,14.4,58.3z M14.4,106.9L14.4,106.9h96.3h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4c0,2.3-1.6,4.1-3.7,4.4v0H111l-0.4,0l-0.4,0H14.7l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,108.9,12,106.9,14.4,106.9z M14.4,155.6L14.4,155.6l153.7,0h3.4h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4c0,2.2-1.6,4.1-3.7,4.4v0h-0.3l-0.4,0l-0.4,0h-2.8l-153.6,0l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,157.6,12,155.6,14.4,155.6z M14.4,204.2L14.4,204.2l153.7,0h3.4h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4s-1.6,4.1-3.7,4.4v0h-0.3l-0.4,0l-0.4,0h-2.8l-153.6,0l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,206.2,12,204.2,14.4,204.2z M176.2,42.9c14,0,26.8,5.7,35.9,14.9C221.3,67,227,79.7,227,93.7c0,14-5.7,26.8-14.9,35.9c-1.8,1.8-3.7,3.5-5.8,5l39,67.5c1.4,2.4,0.6,5.4-1.8,6.7c-2.4,1.4-5.4,0.6-6.7-1.8l-38.9-67.4c-6.6,3.1-13.9,4.8-21.7,4.8c-14,0-26.8-5.7-35.9-14.9c-9.2-9.2-14.9-21.9-14.9-35.9c0-14,5.7-26.8,14.9-35.9C149.4,48.6,162.1,42.9,176.2,42.9L176.2,42.9z M205.1,64.8c-7.4-7.4-17.7-12-28.9-12c-11.3,0-21.5,4.6-28.9,12c-7.4,7.4-12,17.6-12,28.9c0,11.3,4.6,21.5,12,28.9c7.4,7.4,17.6,12,28.9,12c11.3,0,21.5-4.6,28.9-12c7.4-7.4,12-17.6,12-28.9C217.1,82.4,212.5,72.2,205.1,64.8z"/></g></g>
|
|
7
|
+
</svg>
|
|
@@ -3,7 +3,6 @@ import { Slider } from '@miblanchard/react-native-slider';
|
|
|
3
3
|
|
|
4
4
|
import { View } from 'react-native';
|
|
5
5
|
import styles from './SliderRangeTemplateStyles';
|
|
6
|
-
import Text from '../Text';
|
|
7
6
|
import { Colors } from '../../configs';
|
|
8
7
|
import { useConfigGlobalState } from '../../iot/states';
|
|
9
8
|
import { DEVICE_TYPE } from '../../configs/Constants';
|
|
@@ -11,7 +10,7 @@ import _TextInput from '../Form/TextInput';
|
|
|
11
10
|
|
|
12
11
|
const SliderRangeTemplate = memo(
|
|
13
12
|
({ item, doAction, sensor, isWidgetOrder }) => {
|
|
14
|
-
const { configuration
|
|
13
|
+
const { configuration } = item;
|
|
15
14
|
const [configValues] = useConfigGlobalState('configValues');
|
|
16
15
|
const { config, min_value, max_value, action_data } = configuration;
|
|
17
16
|
const [value, setValue] = useState();
|
|
@@ -84,7 +83,6 @@ const SliderRangeTemplate = memo(
|
|
|
84
83
|
<View
|
|
85
84
|
style={(isWidgetOrder && styles.wrapOrderItem) || styles.viewBrightness}
|
|
86
85
|
>
|
|
87
|
-
<Text type="H4">{label}</Text>
|
|
88
86
|
<View style={styles.wrap}>
|
|
89
87
|
<Slider
|
|
90
88
|
step={1}
|
|
@@ -47,7 +47,7 @@ const useNewMessage = (
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
50
|
-
const {
|
|
50
|
+
const { configuration } = item;
|
|
51
51
|
const { action_data, from_config, to_config } = configuration;
|
|
52
52
|
const [configValues] = useConfigGlobalState('configValues');
|
|
53
53
|
const [value, setValue] = useState();
|
|
@@ -140,9 +140,6 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
140
140
|
);
|
|
141
141
|
return (
|
|
142
142
|
<View style={(isWidgetOrder && styles.wrapOrderItem) || styles.wrap}>
|
|
143
|
-
<View>
|
|
144
|
-
<Text type="H4">{label}</Text>
|
|
145
|
-
</View>
|
|
146
143
|
<ScrollView
|
|
147
144
|
ref={scrollViewRef}
|
|
148
145
|
onContentSizeChange={() =>
|
|
@@ -15,7 +15,7 @@ import useKeyboardAnimated from '../../hooks/Explore/useKeyboardAnimated';
|
|
|
15
15
|
const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
16
16
|
const t = useTranslations();
|
|
17
17
|
const transY = useKeyboardAnimated();
|
|
18
|
-
const {
|
|
18
|
+
const { configuration } = item;
|
|
19
19
|
const { action_data, config } = configuration;
|
|
20
20
|
const [configValues] = useConfigGlobalState('configValues');
|
|
21
21
|
const [value, setValue] = useState();
|
|
@@ -35,10 +35,6 @@ const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<View style={(isWidgetOrder && styles.wrapOrderItem) || styles.wrap}>
|
|
38
|
-
<View>
|
|
39
|
-
<Text type="H4">{label}</Text>
|
|
40
|
-
</View>
|
|
41
|
-
|
|
42
38
|
<View style={styles.iconAndText}>
|
|
43
39
|
<Text style={styles.textValue} type="H4">
|
|
44
40
|
{valueText}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from 'react-native';
|
|
8
8
|
import DateTimePickerModal from 'react-native-modal-datetime-picker';
|
|
9
9
|
import renderer, { act } from 'react-test-renderer';
|
|
10
|
+
import WebView from 'react-native-webview';
|
|
10
11
|
|
|
11
12
|
import { Slider } from '@miblanchard/react-native-slider';
|
|
12
13
|
import ActionGroup from '..';
|
|
@@ -606,4 +607,54 @@ describe('Test ActionGroup', () => {
|
|
|
606
607
|
const elements = instance.findAllByType(SwitchButtonTemplate);
|
|
607
608
|
expect(elements).toHaveLength(1);
|
|
608
609
|
});
|
|
610
|
+
|
|
611
|
+
it('test render IframeWithConfig', async () => {
|
|
612
|
+
const actionItem = {
|
|
613
|
+
id: 1,
|
|
614
|
+
template: 'IFrameWithConfig',
|
|
615
|
+
label: 'LED',
|
|
616
|
+
configuration: {
|
|
617
|
+
url: 'http://localhost:3000/',
|
|
618
|
+
actions: [
|
|
619
|
+
{
|
|
620
|
+
action: 'action1-83cd1cf5d8f2',
|
|
621
|
+
name: 'Action 1',
|
|
622
|
+
action_data: {
|
|
623
|
+
id: 2,
|
|
624
|
+
name: 'Action 1',
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
],
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
const mockDoAction = jest.fn();
|
|
631
|
+
await act(async () => {
|
|
632
|
+
wrapper = renderer.create(
|
|
633
|
+
wrapComponent(actionItem, mockDoAction, sensor)
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
const instance = wrapper.root;
|
|
637
|
+
const webview = instance.findByType(WebView);
|
|
638
|
+
|
|
639
|
+
await act(async () => {
|
|
640
|
+
webview.props.onMessage({
|
|
641
|
+
nativeEvent: {
|
|
642
|
+
data: JSON.stringify({
|
|
643
|
+
type: 'triggerAction',
|
|
644
|
+
eraWidgetId: 1,
|
|
645
|
+
source: 'eraIframeWidget',
|
|
646
|
+
data: { actionKey: 'action1-83cd1cf5d8f2' },
|
|
647
|
+
}),
|
|
648
|
+
origin: 'http://localhost',
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
expect(mockDoAction).toHaveBeenCalledTimes(1);
|
|
653
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
654
|
+
actionItem.configuration.actions[0].action_data,
|
|
655
|
+
undefined,
|
|
656
|
+
false,
|
|
657
|
+
undefined
|
|
658
|
+
);
|
|
659
|
+
});
|
|
609
660
|
});
|
|
@@ -15,6 +15,8 @@ import TwoButtonTemplate from './TwoButtonTemplate';
|
|
|
15
15
|
import SwitchButtonTemplate from './OnOffTemplate/SwitchButtonTemplate';
|
|
16
16
|
import TextBoxTemplate from './TextBoxTemplate';
|
|
17
17
|
import TerminalBoxTemplate from './TerminalBoxTemplate';
|
|
18
|
+
import IFrameWithConfig from '../Widgets/IFrameWithConfig/IFrameWithConfig';
|
|
19
|
+
import { WIDGET_TYPE } from '../../configs/Constants';
|
|
18
20
|
|
|
19
21
|
export const getActionComponent = (template) => {
|
|
20
22
|
switch (template) {
|
|
@@ -52,6 +54,8 @@ export const getActionComponent = (template) => {
|
|
|
52
54
|
return TextBoxTemplate;
|
|
53
55
|
case 'TerminalBoxTemplate':
|
|
54
56
|
return TerminalBoxTemplate;
|
|
57
|
+
case WIDGET_TYPE.iframeWithConfig:
|
|
58
|
+
return IFrameWithConfig;
|
|
55
59
|
default:
|
|
56
60
|
return null;
|
|
57
61
|
}
|
|
@@ -17,8 +17,6 @@ import { AccessibilityLabel } from '../../../configs/Constants';
|
|
|
17
17
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
18
18
|
import SharedUnit from '../../Unit/SharedUnit';
|
|
19
19
|
import { axiosGet, fetchWithCache } from '../../../utils/Apis/axios';
|
|
20
|
-
import { preloadImagesFromUnits } from '../../../utils/Functions/preloadImages';
|
|
21
|
-
import { STORAGE_KEY } from '../../../utils/Storage';
|
|
22
20
|
import { SCContext, useSCContextSelector } from '../../../context';
|
|
23
21
|
import { Action } from '../../../context/actionType';
|
|
24
22
|
|
|
@@ -63,14 +61,6 @@ const MyPinnedSharedUnit = ({ refreshing }) => {
|
|
|
63
61
|
}
|
|
64
62
|
}, [fetchSharedUnitDashboard, isFocused, refreshing]);
|
|
65
63
|
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
sharedUnits?.length &&
|
|
68
|
-
preloadImagesFromUnits(
|
|
69
|
-
sharedUnits,
|
|
70
|
-
STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_SHARE_UNIT
|
|
71
|
-
);
|
|
72
|
-
}, [sharedUnits]);
|
|
73
|
-
|
|
74
64
|
return (
|
|
75
65
|
<>
|
|
76
66
|
<Section>
|
|
@@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import renderer, { act } from 'react-test-renderer';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import Carousel from 'react-native-new-snap-carousel';
|
|
6
6
|
import MyUnit from '..';
|
|
7
7
|
import { API } from '../../../../configs';
|
|
8
8
|
import { AccessibilityLabel } from '../../../../configs/Constants';
|
|
@@ -12,13 +12,12 @@ import { flushPromises } from '../../../../screens/AllGateway/test-utils';
|
|
|
12
12
|
import MyUnitDevice from '../../../../screens/Unit/components/MyUnitDevice';
|
|
13
13
|
import api from '../../../../utils/Apis/axios';
|
|
14
14
|
import Routes from '../../../../utils/Route';
|
|
15
|
-
import { removeMultiple, STORAGE_KEY } from '../../../../utils/Storage';
|
|
16
15
|
|
|
17
16
|
const mock = new MockAdapter(api.axiosInstance);
|
|
18
17
|
|
|
19
|
-
const wrapComponent = (data = {}) => (
|
|
18
|
+
const wrapComponent = (data = {}, refreshing = true) => (
|
|
20
19
|
<SCProvider initState={mockSCStore(data)}>
|
|
21
|
-
<MyUnit refreshing={
|
|
20
|
+
<MyUnit refreshing={refreshing} />
|
|
22
21
|
</SCProvider>
|
|
23
22
|
);
|
|
24
23
|
|
|
@@ -27,42 +26,45 @@ describe('Test MyUnit', () => {
|
|
|
27
26
|
let stateData = {
|
|
28
27
|
app: { isDeleteUnitSuccessFully: true, isNeedUpdateCache: true },
|
|
29
28
|
};
|
|
30
|
-
let data =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
is_managed_by_backend: true,
|
|
47
|
-
device_type: '',
|
|
48
|
-
station_name: 'name',
|
|
49
|
-
quick_action: {
|
|
50
|
-
config_id: 1,
|
|
29
|
+
let data = {
|
|
30
|
+
count: 2,
|
|
31
|
+
next: null,
|
|
32
|
+
previous: null,
|
|
33
|
+
results: [
|
|
34
|
+
{
|
|
35
|
+
id: 1,
|
|
36
|
+
name: 'name',
|
|
37
|
+
background: 'background',
|
|
38
|
+
abstract_devices: [
|
|
39
|
+
{
|
|
40
|
+
id: 1,
|
|
41
|
+
name: 'device',
|
|
42
|
+
is_managed_by_backend: true,
|
|
43
|
+
device_type: 'GOOGLE_HOME',
|
|
44
|
+
station_name: 'name',
|
|
51
45
|
},
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
{
|
|
47
|
+
id: 2,
|
|
48
|
+
name: 'device',
|
|
49
|
+
is_managed_by_backend: true,
|
|
50
|
+
device_type: '',
|
|
51
|
+
station_name: 'name',
|
|
52
|
+
quick_action: {
|
|
53
|
+
config_id: 1,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 2,
|
|
60
|
+
name: 'name2',
|
|
61
|
+
background: 'background',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
61
65
|
|
|
62
66
|
beforeEach(async () => {
|
|
63
67
|
mock.resetHistory();
|
|
64
|
-
FastImage.preload.mockClear();
|
|
65
|
-
await removeMultiple([STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_UNITS]);
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
const getElement = (instance) => {
|
|
@@ -88,7 +90,7 @@ describe('Test MyUnit', () => {
|
|
|
88
90
|
});
|
|
89
91
|
|
|
90
92
|
it('MyUnit with unit', async () => {
|
|
91
|
-
mock.onGet(API.
|
|
93
|
+
mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
|
|
92
94
|
await act(async () => {
|
|
93
95
|
tree = await renderer.create(wrapComponent(stateData));
|
|
94
96
|
});
|
|
@@ -101,16 +103,18 @@ describe('Test MyUnit', () => {
|
|
|
101
103
|
accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-0`,
|
|
102
104
|
});
|
|
103
105
|
await act(async () => {
|
|
104
|
-
await button.props.onPress(data[0]);
|
|
106
|
+
await button.props.onPress(data.results[0]);
|
|
105
107
|
});
|
|
106
108
|
expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
|
|
107
109
|
params: { unitId: 1 },
|
|
108
110
|
screen: Routes.UnitDetail,
|
|
109
111
|
});
|
|
112
|
+
expect(mock.history.get).toHaveLength(1);
|
|
113
|
+
expect(mock.history.get[0].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
|
|
110
114
|
});
|
|
111
115
|
|
|
112
116
|
it('Test isNeedUpdateCache = false', async () => {
|
|
113
|
-
mock.onGet(API.
|
|
117
|
+
mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
|
|
114
118
|
await act(async () => {
|
|
115
119
|
tree = await renderer.create(
|
|
116
120
|
wrapComponent({ app: { isNeedUpdateCache: false } })
|
|
@@ -118,10 +122,10 @@ describe('Test MyUnit', () => {
|
|
|
118
122
|
});
|
|
119
123
|
const instance = tree.root;
|
|
120
124
|
const button = instance.findByProps({
|
|
121
|
-
accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-
|
|
125
|
+
accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-1`,
|
|
122
126
|
});
|
|
123
127
|
await act(async () => {
|
|
124
|
-
await button.props.onPress(data[1]);
|
|
128
|
+
await button.props.onPress(data.results[1]);
|
|
125
129
|
});
|
|
126
130
|
expect(global.mockedNavigate).toHaveBeenCalledWith('UnitStack', {
|
|
127
131
|
params: { unitId: 2 },
|
|
@@ -129,18 +133,80 @@ describe('Test MyUnit', () => {
|
|
|
129
133
|
});
|
|
130
134
|
});
|
|
131
135
|
|
|
132
|
-
it('Test
|
|
133
|
-
mock.onGet(API.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
it('Test fetch when refreshing = false', async () => {
|
|
137
|
+
mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
|
|
138
|
+
await act(async () => {
|
|
139
|
+
tree = await renderer.create(
|
|
140
|
+
wrapComponent({ app: { isNeedUpdateCache: false } }, false)
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
expect(mock.history.get).toHaveLength(1);
|
|
144
|
+
expect(mock.history.get[0].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('Test fetch unit data when snap to item', async () => {
|
|
148
|
+
const base = data.results[0];
|
|
149
|
+
const extraIds = Array.from({ length: 9 }, (_, i) => i + 3);
|
|
150
|
+
const extraResults = extraIds.map((id) => ({
|
|
151
|
+
...base,
|
|
152
|
+
id,
|
|
153
|
+
}));
|
|
154
|
+
mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, {
|
|
155
|
+
...data,
|
|
156
|
+
count: 11,
|
|
157
|
+
results: [...data.results, ...extraResults],
|
|
136
158
|
});
|
|
159
|
+
await act(async () => {
|
|
160
|
+
tree = await renderer.create(
|
|
161
|
+
wrapComponent({ app: { isNeedUpdateCache: false } }, false)
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
const instance = tree.root;
|
|
165
|
+
const carousel = instance.findByType(Carousel);
|
|
166
|
+
|
|
167
|
+
await act(() => {
|
|
168
|
+
carousel.props.onSnapToItem(3);
|
|
169
|
+
});
|
|
170
|
+
await act(() => {
|
|
171
|
+
carousel.props.onSnapToItem(4);
|
|
172
|
+
});
|
|
173
|
+
expect(mock.history.get).toHaveLength(1);
|
|
174
|
+
expect(mock.history.get[0].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
|
|
175
|
+
await act(() => {
|
|
176
|
+
carousel.props.onSnapToItem(5);
|
|
177
|
+
});
|
|
178
|
+
await act(() => {
|
|
179
|
+
carousel.props.onSnapToItem(6);
|
|
180
|
+
});
|
|
181
|
+
expect(mock.history.get).toHaveLength(2);
|
|
182
|
+
expect(mock.history.get[1].url).toEqual(API.UNIT_V2.MY_UNITS(2, 10));
|
|
183
|
+
await act(() => {
|
|
184
|
+
carousel.props.onSnapToItem(4);
|
|
185
|
+
});
|
|
186
|
+
await act(() => {
|
|
187
|
+
carousel.props.onSnapToItem(3);
|
|
188
|
+
});
|
|
189
|
+
expect(mock.history.get).toHaveLength(3);
|
|
190
|
+
expect(mock.history.get[2].url).toEqual(API.UNIT_V2.MY_UNITS(1, 10));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('Test navigation select unit', async () => {
|
|
194
|
+
mock.onGet(API.UNIT_V2.MY_UNITS(1, 10)).replyOnce(200, data);
|
|
137
195
|
await act(async () => {
|
|
138
196
|
tree = await renderer.create(
|
|
139
197
|
wrapComponent({ app: { isNeedUpdateCache: false } })
|
|
140
198
|
);
|
|
141
199
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
200
|
+
|
|
201
|
+
const instance = tree.root;
|
|
202
|
+
const button = instance.findByProps({
|
|
203
|
+
accessibilityLabel: AccessibilityLabel.SELECT_UNIT,
|
|
204
|
+
});
|
|
205
|
+
await act(async () => {
|
|
206
|
+
await button.props.onPress();
|
|
207
|
+
});
|
|
208
|
+
expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
|
|
209
|
+
screen: Routes.GoToDetailUnit,
|
|
210
|
+
});
|
|
145
211
|
});
|
|
146
212
|
});
|
|
@@ -23,13 +23,13 @@ import { Action } from '../../../context/actionType';
|
|
|
23
23
|
import { BleManager } from 'react-native-ble-plx';
|
|
24
24
|
import Carousel from 'react-native-new-snap-carousel';
|
|
25
25
|
import FImage from '../../FImage';
|
|
26
|
+
import SearchMenu from '../../../Images/Common/search-menu.svg';
|
|
26
27
|
import MyUnitDevice from '../../../screens/Unit/components/MyUnitDevice';
|
|
27
28
|
import NetInfo from '@react-native-community/netinfo';
|
|
29
|
+
import { PAGE_SIZE } from '../../../configs/Constants';
|
|
28
30
|
import Routes from '../../../utils/Route';
|
|
29
|
-
import { STORAGE_KEY } from '../../../utils/Storage';
|
|
30
31
|
import { Section } from '../../Section';
|
|
31
32
|
import Text from '../../Text';
|
|
32
|
-
import { preloadImagesFromUnits } from '../../../utils/Functions/preloadImages';
|
|
33
33
|
import styles from './styles';
|
|
34
34
|
import { usePrevious } from '../../../hooks';
|
|
35
35
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
@@ -44,7 +44,9 @@ const MyUnit = ({ refreshing }) => {
|
|
|
44
44
|
const t = useTranslations();
|
|
45
45
|
const isFocused = useIsFocused();
|
|
46
46
|
const navigation = useNavigation();
|
|
47
|
-
const [
|
|
47
|
+
const [page, setPage] = useState(1);
|
|
48
|
+
const [maxPageUnit, setMaxPageUnit] = useState(1);
|
|
49
|
+
const [unitsByPage, setUnitsByPage] = useState(() => new Map());
|
|
48
50
|
const previousMyUnits = usePrevious(myUnits);
|
|
49
51
|
const [slideIndex, setSlideIndex] = useState(0);
|
|
50
52
|
const { setAction } = useContext(SCContext);
|
|
@@ -54,6 +56,10 @@ const MyUnit = ({ refreshing }) => {
|
|
|
54
56
|
const isNeedUpdateCache = useSCContextSelector(
|
|
55
57
|
(state) => state?.app?.isNeedUpdateCache
|
|
56
58
|
);
|
|
59
|
+
const myUnits = useMemo(() => {
|
|
60
|
+
const pages = [...unitsByPage.keys()];
|
|
61
|
+
return pages.flatMap((p) => unitsByPage.get(p));
|
|
62
|
+
}, [unitsByPage]);
|
|
57
63
|
|
|
58
64
|
const {
|
|
59
65
|
permissionsRequested: bluetoothPermRequested,
|
|
@@ -64,25 +70,51 @@ const MyUnit = ({ refreshing }) => {
|
|
|
64
70
|
!bluetoothPermRequested && requestBluetoothPerm();
|
|
65
71
|
}, [bluetoothPermRequested, requestBluetoothPerm]);
|
|
66
72
|
|
|
67
|
-
const fetchMyUnitDashboard = useCallback(async () => {
|
|
73
|
+
const fetchMyUnitDashboard = useCallback(async (pageParam) => {
|
|
74
|
+
const handleSuccess = (data) => {
|
|
75
|
+
setUnitsByPage((prev) => {
|
|
76
|
+
const next = new Map(prev);
|
|
77
|
+
next.set(pageParam, data.results);
|
|
78
|
+
return next;
|
|
79
|
+
});
|
|
80
|
+
setMaxPageUnit(Math.ceil(data.count / PAGE_SIZE));
|
|
81
|
+
};
|
|
82
|
+
|
|
68
83
|
if (isNeedUpdateCache) {
|
|
69
84
|
setAction(Action.IS_CHECK_CLEAR_CACHE_UNITS, false);
|
|
70
|
-
const { success, data } = await axiosGet(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
const { success, data } = await axiosGet(
|
|
86
|
+
API.UNIT_V2.MY_UNITS(pageParam, PAGE_SIZE),
|
|
87
|
+
{},
|
|
88
|
+
true
|
|
89
|
+
);
|
|
90
|
+
success && handleSuccess(data);
|
|
75
91
|
} else {
|
|
76
|
-
await fetchWithCache(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
await fetchWithCache(
|
|
93
|
+
API.UNIT_V2.MY_UNITS(pageParam, PAGE_SIZE),
|
|
94
|
+
{},
|
|
95
|
+
(response) => {
|
|
96
|
+
const { success, data } = response;
|
|
97
|
+
success && handleSuccess(data);
|
|
81
98
|
}
|
|
82
|
-
|
|
99
|
+
);
|
|
83
100
|
}
|
|
84
101
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
85
|
-
}, [
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
const onSnapToItem = useCallback(
|
|
105
|
+
(index) => {
|
|
106
|
+
const isUp = index > slideIndex;
|
|
107
|
+
const mid = page * PAGE_SIZE - PAGE_SIZE / 2;
|
|
108
|
+
if (isUp && index >= mid && page < maxPageUnit) {
|
|
109
|
+
setPage((prev) => prev + 1);
|
|
110
|
+
}
|
|
111
|
+
if (!isUp && index < mid && page > 1) {
|
|
112
|
+
setPage((prev) => prev - 1);
|
|
113
|
+
}
|
|
114
|
+
setSlideIndex(index);
|
|
115
|
+
},
|
|
116
|
+
[page, slideIndex, maxPageUnit]
|
|
117
|
+
);
|
|
86
118
|
|
|
87
119
|
useFocusEffect(
|
|
88
120
|
useCallback(() => {
|
|
@@ -136,6 +168,12 @@ const MyUnit = ({ refreshing }) => {
|
|
|
136
168
|
[navigation]
|
|
137
169
|
);
|
|
138
170
|
|
|
171
|
+
const goToSelectUnit = useCallback(() => {
|
|
172
|
+
navigation.navigate(Routes.UnitStack, {
|
|
173
|
+
screen: Routes.GoToDetailUnit,
|
|
174
|
+
});
|
|
175
|
+
}, [navigation]);
|
|
176
|
+
|
|
139
177
|
const _renderItem = useCallback(
|
|
140
178
|
({ item, index }) => {
|
|
141
179
|
const paddingLeft = index === 0 ? 0 : 8;
|
|
@@ -176,15 +214,15 @@ const MyUnit = ({ refreshing }) => {
|
|
|
176
214
|
);
|
|
177
215
|
|
|
178
216
|
useEffect(() => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
useEffect(() => {
|
|
217
|
+
let pageParam = page;
|
|
218
|
+
if (refreshing) {
|
|
219
|
+
pageParam = Math.floor(slideIndex / PAGE_SIZE) + 1;
|
|
220
|
+
}
|
|
184
221
|
if (isFocused || refreshing) {
|
|
185
|
-
fetchMyUnitDashboard();
|
|
222
|
+
fetchMyUnitDashboard(pageParam);
|
|
186
223
|
}
|
|
187
|
-
|
|
224
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
225
|
+
}, [fetchMyUnitDashboard, page, isFocused, refreshing]);
|
|
188
226
|
|
|
189
227
|
useEffect(() => {
|
|
190
228
|
if (isDeleteUnitSuccessFully || previousMyUnits?.length > myUnits?.length) {
|
|
@@ -201,9 +239,18 @@ const MyUnit = ({ refreshing }) => {
|
|
|
201
239
|
return (
|
|
202
240
|
<>
|
|
203
241
|
<Section style={styles.boxTxtMyUnit}>
|
|
204
|
-
<
|
|
205
|
-
{
|
|
206
|
-
|
|
242
|
+
<View style={styles.titleWrap}>
|
|
243
|
+
<Text style={styles.unitsHeading} type="H5" color={Colors.Gray8}>
|
|
244
|
+
{t('text_my_units')}
|
|
245
|
+
</Text>
|
|
246
|
+
<TouchableOpacity
|
|
247
|
+
onPress={goToSelectUnit}
|
|
248
|
+
style={styles.searchMenuButton}
|
|
249
|
+
accessibilityLabel={AccessibilityLabel.SELECT_UNIT}
|
|
250
|
+
>
|
|
251
|
+
<SearchMenu style={styles.searchMenuIcon} />
|
|
252
|
+
</TouchableOpacity>
|
|
253
|
+
</View>
|
|
207
254
|
<View style={styles.container}>
|
|
208
255
|
{myUnits.length ? (
|
|
209
256
|
<Carousel
|
|
@@ -213,7 +260,7 @@ const MyUnit = ({ refreshing }) => {
|
|
|
213
260
|
itemWidth={screenWidth - 32}
|
|
214
261
|
renderItem={_renderItem}
|
|
215
262
|
inactiveSlideScale={1}
|
|
216
|
-
onSnapToItem={
|
|
263
|
+
onSnapToItem={onSnapToItem}
|
|
217
264
|
/>
|
|
218
265
|
) : (
|
|
219
266
|
<View>
|
|
@@ -14,7 +14,7 @@ const styles = StyleSheet.create({
|
|
|
14
14
|
alignItems: 'center',
|
|
15
15
|
paddingHorizontal: 16,
|
|
16
16
|
paddingBottom: 8,
|
|
17
|
-
paddingTop:
|
|
17
|
+
paddingTop: 8,
|
|
18
18
|
},
|
|
19
19
|
container: {
|
|
20
20
|
flex: 1,
|
|
@@ -52,10 +52,25 @@ const styles = StyleSheet.create({
|
|
|
52
52
|
bottom: 16,
|
|
53
53
|
left: 16,
|
|
54
54
|
},
|
|
55
|
+
titleWrap: {
|
|
56
|
+
flexDirection: 'row',
|
|
57
|
+
justifyContent: 'space-between',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
},
|
|
55
60
|
btnItem: {
|
|
56
61
|
height: 121,
|
|
57
62
|
marginBottom: 16,
|
|
58
63
|
},
|
|
64
|
+
searchMenuButton: {
|
|
65
|
+
width: 40,
|
|
66
|
+
height: 40,
|
|
67
|
+
justifyContent: 'center',
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
},
|
|
70
|
+
searchMenuIcon: {
|
|
71
|
+
width: 22,
|
|
72
|
+
height: 22,
|
|
73
|
+
},
|
|
59
74
|
});
|
|
60
75
|
|
|
61
76
|
export default styles;
|