@eohjsc/react-native-smart-city 0.7.24 → 0.7.26

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 (54) hide show
  1. package/index.js +2 -0
  2. package/package.json +1 -1
  3. package/src/Images/Common/search-menu.svg +7 -0
  4. package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplateStyle.js +2 -1
  5. package/src/commons/ActionGroup/OneBigButtonTemplateStyle.js +1 -2
  6. package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -3
  7. package/src/commons/ActionGroup/TerminalBoxTemplate.js +1 -4
  8. package/src/commons/ActionGroup/TextBoxTemplate.js +1 -5
  9. package/src/commons/ActionGroup/ThreeButtonTemplate/components/ThreeButtonDefaultStyles.js +1 -1
  10. package/src/commons/ActionGroup/__test__/index.test.js +51 -0
  11. package/src/commons/ActionGroup/index.js +4 -0
  12. package/src/commons/Dashboard/MyDashboardDevice/__test__/index.test.js +171 -0
  13. package/src/commons/Dashboard/MyDashboardDevice/index.js +218 -0
  14. package/src/commons/Dashboard/MyDashboardDevice/styles.js +60 -0
  15. package/src/commons/Dashboard/MyPinnedSharedUnit/index.js +0 -10
  16. package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +114 -48
  17. package/src/commons/Dashboard/MyUnit/index.js +74 -27
  18. package/src/commons/Dashboard/MyUnit/styles.js +16 -1
  19. package/src/commons/DateTimeRangeChange/index.js +1 -1
  20. package/src/commons/Device/ItemDevice.js +12 -3
  21. package/src/commons/Device/ItemDeviceWrapper.js +10 -2
  22. package/src/commons/SelectUnit/index.js +19 -5
  23. package/src/commons/SelectUnit/styles.js +0 -1
  24. package/src/commons/SubUnit/DeviceTemplate/DeviceTemplate.js +8 -3
  25. package/src/commons/Widgets/IFrame/IFrameStyles.js +1 -0
  26. package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfigStyles.js +1 -0
  27. package/src/configs/API.js +11 -0
  28. package/src/configs/AccessibilityLabel.js +2 -0
  29. package/src/configs/Constants.js +3 -0
  30. package/src/context/actionType.ts +4 -0
  31. package/src/context/mockStore.ts +5 -0
  32. package/src/context/reducer.ts +30 -5
  33. package/src/iot/mqtt.js +163 -47
  34. package/src/navigations/UnitStack.js +22 -0
  35. package/src/screens/Device/__test__/detail.test.js +42 -1
  36. package/src/screens/Device/__test__/mqttDetail.test.js +411 -190
  37. package/src/screens/Device/__test__/sensorDisplayItem.test.js +27 -0
  38. package/src/screens/Device/components/DonutChart.js +5 -14
  39. package/src/screens/Device/components/SensorDisplayItem.js +92 -61
  40. package/src/screens/Device/components/VisualChart.js +0 -12
  41. package/src/screens/Device/detail.js +50 -14
  42. package/src/screens/Device/hooks/useDashboardDevice.js +34 -0
  43. package/src/screens/Device/styles.js +16 -0
  44. package/src/screens/SubUnit/AddSubUnit.js +18 -8
  45. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +5 -3
  46. package/src/screens/Unit/GoToDetailUnit.js +30 -0
  47. package/src/screens/Unit/__test__/GoToDetailUnit.test.js +103 -0
  48. package/src/utils/FactoryGateway.js +105 -0
  49. package/src/utils/I18n/translations/en.js +4 -0
  50. package/src/utils/I18n/translations/vi.js +4 -0
  51. package/src/utils/Route/index.js +1 -0
  52. package/src/utils/Storage.js +18 -4
  53. package/src/utils/Utils.js +2 -1
  54. package/src/utils/Functions/preloadImages.js +0 -38
@@ -18,6 +18,7 @@ import MockAdapter from 'axios-mock-adapter';
18
18
  import api from '../../../utils/Apis/axios';
19
19
  import CurrentRainSensor from '../../../commons/Device/RainningSensor/CurrentRainSensor';
20
20
  import LabelValue from '../../../commons/Device/LabelValue';
21
+ import Text from '../../../commons/Text';
21
22
 
22
23
  jest.mock('../../../iot/states', () => ({
23
24
  useConfigGlobalState: () => [{}, null],
@@ -51,6 +52,7 @@ describe('Test SensorDisplayItem', () => {
51
52
  },
52
53
  ],
53
54
  },
55
+ label: 'Chart',
54
56
  is_configuration_ready: true,
55
57
  };
56
58
 
@@ -65,6 +67,8 @@ describe('Test SensorDisplayItem', () => {
65
67
  const instance = tree.root;
66
68
  const visualChart = instance.findAllByType(VisualChart);
67
69
  expect(visualChart).toHaveLength(1);
70
+ const labels = instance.findAllByType(Text);
71
+ expect(labels[0].props.children).toEqual('Chart');
68
72
  });
69
73
 
70
74
  it('render ActionGroup', async () => {
@@ -82,6 +86,7 @@ describe('Test SensorDisplayItem', () => {
82
86
  },
83
87
  ],
84
88
  },
89
+ label: 'LED',
85
90
  is_configuration_ready: true,
86
91
  };
87
92
 
@@ -96,6 +101,8 @@ describe('Test SensorDisplayItem', () => {
96
101
  const instance = tree.root;
97
102
  const actionGroup = instance.findAllByType(ActionGroup);
98
103
  expect(actionGroup).toHaveLength(1);
104
+ const labels = instance.findAllByType(Text);
105
+ expect(labels[0].props.children).toEqual('LED');
99
106
  });
100
107
 
101
108
  it('render camera', async () => {
@@ -113,6 +120,7 @@ describe('Test SensorDisplayItem', () => {
113
120
  },
114
121
  ],
115
122
  },
123
+ label: 'Camera',
116
124
  is_configuration_ready: true,
117
125
  };
118
126
 
@@ -127,6 +135,8 @@ describe('Test SensorDisplayItem', () => {
127
135
  const instance = tree.root;
128
136
  const mediaPlayerDetail = instance.findAllByType(MediaPlayerDetail);
129
137
  expect(mediaPlayerDetail).toHaveLength(1);
138
+ const labels = instance.findAllByType(Text);
139
+ expect(labels[0].props.children).toEqual('Camera');
130
140
  });
131
141
 
132
142
  it('render flat_list', async () => {
@@ -213,6 +223,7 @@ describe('Test SensorDisplayItem', () => {
213
223
  configuration: {
214
224
  template: 'gauge',
215
225
  },
226
+ label: 'Gauge',
216
227
  is_configuration_ready: true,
217
228
  };
218
229
 
@@ -227,6 +238,8 @@ describe('Test SensorDisplayItem', () => {
227
238
  const instance = tree.root;
228
239
  const flatListItems = instance.findAllByType(Anemometer);
229
240
  expect(flatListItems).toHaveLength(1);
241
+ const labels = instance.findAllByType(Text);
242
+ expect(labels[0].props.children).toEqual('Gauge');
230
243
  });
231
244
 
232
245
  it('test render progress_bar', async () => {
@@ -237,6 +250,7 @@ describe('Test SensorDisplayItem', () => {
237
250
  configuration: {
238
251
  template: 'progress_bar',
239
252
  },
253
+ label: 'Brightness',
240
254
  is_configuration_ready: true,
241
255
  };
242
256
 
@@ -251,6 +265,8 @@ describe('Test SensorDisplayItem', () => {
251
265
  const instance = tree.root;
252
266
  const flatListItems = instance.findAllByType(ProgressBar);
253
267
  expect(flatListItems).toHaveLength(1);
268
+ const labels = instance.findAllByType(Text);
269
+ expect(labels[0].props.children).toEqual('Brightness');
254
270
  });
255
271
 
256
272
  it('test render Compass', async () => {
@@ -261,6 +277,7 @@ describe('Test SensorDisplayItem', () => {
261
277
  configuration: {
262
278
  template: 'compass',
263
279
  },
280
+ label: 'Compass',
264
281
  is_configuration_ready: true,
265
282
  };
266
283
 
@@ -275,6 +292,8 @@ describe('Test SensorDisplayItem', () => {
275
292
  const instance = tree.root;
276
293
  const flatListItems = instance.findAllByType(Compass);
277
294
  expect(flatListItems).toHaveLength(1);
295
+ const labels = instance.findAllByType(Text);
296
+ expect(labels[0].props.children).toEqual('Compass');
278
297
  });
279
298
 
280
299
  it('test render alert_status', async () => {
@@ -314,6 +333,8 @@ describe('Test SensorDisplayItem', () => {
314
333
 
315
334
  const currentRainSensor = instance.findByType(CurrentRainSensor);
316
335
  expect(currentRainSensor.props).toEqual(expectProps);
336
+ const labels = instance.findAllByType(Text);
337
+ expect(labels[0].props.children).toEqual('Circle');
317
338
  };
318
339
 
319
340
  it('render circle_mini', async () => {
@@ -332,6 +353,7 @@ describe('Test SensorDisplayItem', () => {
332
353
  color: 'blue',
333
354
  },
334
355
  },
356
+ label: 'Circle',
335
357
  is_configuration_ready: true,
336
358
  };
337
359
 
@@ -371,6 +393,7 @@ describe('Test SensorDisplayItem', () => {
371
393
  color: 'blue',
372
394
  },
373
395
  },
396
+ label: 'Circle',
374
397
  is_configuration_ready: true,
375
398
  };
376
399
 
@@ -400,6 +423,7 @@ describe('Test SensorDisplayItem', () => {
400
423
  configuration: {
401
424
  config: null,
402
425
  },
426
+ label: 'Circle',
403
427
  is_configuration_ready: true,
404
428
  };
405
429
 
@@ -417,6 +441,7 @@ describe('Test SensorDisplayItem', () => {
417
441
  order: 0,
418
442
  template: 'LabelValue',
419
443
  configuration: {},
444
+ label: 'Label',
420
445
  is_configuration_ready: true,
421
446
  };
422
447
 
@@ -431,5 +456,7 @@ describe('Test SensorDisplayItem', () => {
431
456
  const instance = tree.root;
432
457
  const displayItems = instance.findAllByType(LabelValue);
433
458
  expect(displayItems).toHaveLength(1);
459
+ const labels = instance.findAllByType(Text);
460
+ expect(labels[0].props.children).toEqual('Label');
434
461
  });
435
462
  });
@@ -1,7 +1,6 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { StyleSheet, View } from 'react-native';
3
3
  import { Colors } from '../../../configs';
4
- import Text from '../../../commons/Text';
5
4
  import Highcharts from '../../../commons/Highcharts';
6
5
  import { useConfigGlobalState } from '../../../iot/states';
7
6
 
@@ -63,7 +62,7 @@ const chartOptions = {
63
62
  };
64
63
 
65
64
  const DonutCharts = ({ item, isWidgetOrder }) => {
66
- const { configuration, label } = item;
65
+ const { configuration } = item;
67
66
  const { configs = [] } = configuration;
68
67
  const [configValues] = useConfigGlobalState('configValues');
69
68
  const [highchartsWebviewRef, setHighchartsWebviewRef] = useState(null);
@@ -92,13 +91,7 @@ const DonutCharts = ({ item, isWidgetOrder }) => {
92
91
  }, [configs, getConfigValue, highchartsWebviewRef]);
93
92
 
94
93
  return (
95
- <View>
96
- <View style={styles.titleHistory}>
97
- <Text type="H3" semibold color={Colors.Gray9}>
98
- {label}
99
- </Text>
100
- </View>
101
-
94
+ <View style={styles.wrapDonut}>
102
95
  <Highcharts
103
96
  setRef={setHighchartsWebviewRef}
104
97
  options={chartOptions}
@@ -113,15 +106,13 @@ const DonutCharts = ({ item, isWidgetOrder }) => {
113
106
  };
114
107
 
115
108
  const styles = StyleSheet.create({
109
+ wrapDonut: {
110
+ marginBottom: 16,
111
+ },
116
112
  chartStyle: {
117
113
  backgroundColor: Colors.White,
118
114
  flex: 1,
119
115
  },
120
- titleHistory: {
121
- flexDirection: 'row',
122
- justifyContent: 'space-between',
123
- paddingHorizontal: 16,
124
- },
125
116
  webviewStyle: {
126
117
  flex: 1,
127
118
  minHeight: 200,
@@ -15,7 +15,7 @@ import Compass from '../../../commons/Device/WindDirection/Compass';
15
15
  import Anemometer from '../../../commons/Device/WindSpeed/Anemometer';
16
16
  import MediaPlayerDetail from '../../../commons/MediaPlayerDetail';
17
17
  import IFrame from '../../../commons/Widgets/IFrame/IFrame';
18
- import IFrameWithConfig from '../../../commons/Widgets/IFrameWithConfig/IFrameWithConfig';
18
+ import Text from '../../../commons/Text';
19
19
  import { Device } from '../../../configs';
20
20
  import { AccessibilityLabel, WIDGET_TYPE } from '../../../configs/Constants';
21
21
  import { useSCContextSelector } from '../../../context';
@@ -44,7 +44,7 @@ export const SensorDisplayItem = ({
44
44
  isWidgetOrder,
45
45
  }) => {
46
46
  const userId = useSCContextSelector((state) => state.auth.account.user.id);
47
- const { configuration = {}, id: idTemplate } = item;
47
+ const { configuration = {}, id: idTemplate, label } = item;
48
48
  const { type, template, uri, id, name, title, value_evaluation } =
49
49
  configuration;
50
50
 
@@ -137,6 +137,7 @@ export const SensorDisplayItem = ({
137
137
  <View
138
138
  style={[styles.mediaContainer, isWidgetOrder && styles.cameraOrder]}
139
139
  >
140
+ <Text style={styles.widgetLabelCamera}>{label}</Text>
140
141
  <MediaPlayerDetail
141
142
  uri={uri}
142
143
  thumbnail={{
@@ -169,92 +170,117 @@ export const SensorDisplayItem = ({
169
170
  case 'switch_button_action_template':
170
171
  case 'TextBoxTemplate':
171
172
  case 'TerminalBoxTemplate':
173
+ case WIDGET_TYPE.iframeWithConfig:
172
174
  return (
173
- <ActionGroup
174
- accessibilityLabel={AccessibilityLabel.DEVICE_DETAIL_ACTION_GROUP}
175
- doAction={doAction}
176
- sensor={sensor}
177
- id={idTemplate}
178
- item={item}
179
- isWidgetOrder={isWidgetOrder}
180
- />
175
+ <View>
176
+ <Text style={styles.widgetLabel}>{label}</Text>
177
+ <ActionGroup
178
+ accessibilityLabel={AccessibilityLabel.DEVICE_DETAIL_ACTION_GROUP}
179
+ doAction={doAction}
180
+ sensor={sensor}
181
+ id={idTemplate}
182
+ item={item}
183
+ isWidgetOrder={isWidgetOrder}
184
+ />
185
+ </View>
181
186
  );
182
187
  case WIDGET_TYPE.iframe:
183
188
  return (
184
- <IFrame
185
- doAction={doAction}
186
- sensor={sensor}
187
- id={idTemplate}
188
- item={item}
189
- isWidgetOrder={isWidgetOrder}
190
- />
191
- );
192
- case WIDGET_TYPE.iframeWithConfig:
193
- return (
194
- <IFrameWithConfig
195
- doAction={doAction}
196
- sensor={sensor}
197
- id={idTemplate}
198
- item={item}
199
- isWidgetOrder={isWidgetOrder}
200
- />
189
+ <View>
190
+ <Text style={styles.widgetLabel}>{label}</Text>
191
+ <IFrame
192
+ doAction={doAction}
193
+ sensor={sensor}
194
+ id={idTemplate}
195
+ item={item}
196
+ isWidgetOrder={isWidgetOrder}
197
+ />
198
+ </View>
201
199
  );
202
200
  case 'history':
203
201
  return (
204
- <VisualChart
205
- item={item}
206
- sensor={sensor}
207
- isWidgetOrder={isWidgetOrder}
208
- />
202
+ <View>
203
+ <Text style={styles.widgetLabelHistory}>{label}</Text>
204
+ <VisualChart
205
+ item={item}
206
+ sensor={sensor}
207
+ isWidgetOrder={isWidgetOrder}
208
+ />
209
+ </View>
209
210
  );
210
211
  case 'circle_mini':
211
212
  return (
212
- <CurrentRainSensor
213
- size={120}
214
- textType="H4"
215
- iconSize={25}
216
- data={getDataCircleMini(item)}
217
- isWidgetOrder={isWidgetOrder}
218
- />
213
+ <View>
214
+ <Text style={styles.widgetLabel}>{label}</Text>
215
+ <CurrentRainSensor
216
+ size={120}
217
+ textType="H4"
218
+ iconSize={25}
219
+ data={getDataCircleMini(item)}
220
+ isWidgetOrder={isWidgetOrder}
221
+ />
222
+ </View>
219
223
  );
220
224
  // use the same method to get data for circle_mini
221
225
  case 'LabelValue':
222
226
  return (
223
- <LabelValue
224
- data={getDataCircleMini(item)}
225
- item={item}
226
- isWidgetOrder={isWidgetOrder}
227
- />
227
+ <View>
228
+ <Text style={styles.widgetLabel}>{label}</Text>
229
+ <LabelValue
230
+ data={getDataCircleMini(item)}
231
+ item={item}
232
+ isWidgetOrder={isWidgetOrder}
233
+ />
234
+ </View>
228
235
  );
229
236
  case 'value':
230
237
  switch (type || template) {
231
238
  case 'circle':
232
239
  return (
233
- <CurrentRainSensor
234
- data={getData(item)}
235
- isWidgetOrder={isWidgetOrder}
236
- />
240
+ <View>
241
+ <Text style={styles.widgetLabel}>{label}</Text>
242
+ <CurrentRainSensor
243
+ data={getData(item)}
244
+ isWidgetOrder={isWidgetOrder}
245
+ />
246
+ </View>
237
247
  );
238
248
  case 'simple_list':
239
249
  return (
240
- <PMSensorIndicator
241
- data={getData(item)}
242
- style={styles.simpleList}
243
- isWidgetOrder={isWidgetOrder}
244
- />
250
+ <View>
251
+ <Text style={styles.widgetLabel}>{label}</Text>
252
+ <PMSensorIndicator
253
+ data={getData(item)}
254
+ style={styles.simpleList}
255
+ isWidgetOrder={isWidgetOrder}
256
+ />
257
+ </View>
245
258
  );
246
259
  case 'gauge':
247
- return <Anemometer data={getData(item)} item={item} />;
260
+ return (
261
+ <View>
262
+ <Text style={styles.widgetLabel}>{label}</Text>
263
+ <Anemometer data={getData(item)} item={item} />
264
+ </View>
265
+ );
248
266
  case 'progress_bar':
249
267
  return (
250
- <ProgressBar
251
- data={getData(item)}
252
- item={item}
253
- isWidgetOrder={isWidgetOrder}
254
- />
268
+ <View>
269
+ <Text style={styles.widgetLabel}>{label}</Text>
270
+ <ProgressBar
271
+ data={getData(item)}
272
+ item={item}
273
+ isWidgetOrder={isWidgetOrder}
274
+ />
275
+ </View>
255
276
  );
256
277
  case 'compass':
257
- return <Compass data={getData(item)} isWidgetOrder={isWidgetOrder} />;
278
+ return (
279
+ <View>
280
+ <Text style={styles.widgetLabel}>{label}</Text>
281
+ <Compass data={getData(item)} isWidgetOrder={isWidgetOrder} />
282
+ </View>
283
+ );
258
284
  case 'alert_status':
259
285
  return (
260
286
  <DeviceAlertStatus
@@ -274,7 +300,12 @@ export const SensorDisplayItem = ({
274
300
  />
275
301
  );
276
302
  case 'donut_chart_race':
277
- return <DonutCharts item={item} isWidgetOrder={isWidgetOrder} />;
303
+ return (
304
+ <View>
305
+ <Text style={styles.widgetLabel}>{label}</Text>
306
+ <DonutCharts item={item} isWidgetOrder={isWidgetOrder} />
307
+ </View>
308
+ );
278
309
  default:
279
310
  return <ListQualityIndicator data={getData(item)} />;
280
311
  }
@@ -13,7 +13,6 @@ import { useFetchConfigHistory } from '../../../commons/UnitSummary/ConfigHistor
13
13
  import { StyleSheet, View } from 'react-native';
14
14
  import { Colors } from '../../../configs';
15
15
  import ChartAggregationOption from '../../../commons/ChartAggregationOption';
16
- import Text from '../../../commons/Text';
17
16
  import { CHART_GROUP_TYPE, CHART_TIME } from '../../../configs/Constants';
18
17
  import { getDateRangeOfWeek } from '../../../utils/Utils';
19
18
  import Highcharts from '../../../commons/Highcharts';
@@ -113,16 +112,10 @@ const styles = StyleSheet.create({
113
112
  backgroundColor: Colors.White,
114
113
  flex: 1,
115
114
  },
116
- titleHistory: {
117
- flexDirection: 'row',
118
- justifyContent: 'space-between',
119
- paddingHorizontal: 16,
120
- },
121
115
  aggregationView: {
122
116
  flexDirection: 'row',
123
117
  justifyContent: 'space-between',
124
118
  paddingHorizontal: 16,
125
- paddingTop: 8,
126
119
  alignSelf: 'flex-end',
127
120
  },
128
121
  webviewStyle: {
@@ -312,11 +305,6 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
312
305
 
313
306
  return (
314
307
  <View style={styles.container}>
315
- <View style={styles.titleHistory}>
316
- <Text type="H3" semibold color={Colors.Gray9}>
317
- {item.label}
318
- </Text>
319
- </View>
320
308
  <View style={styles.aggregationView}>
321
309
  {canChooseGroup && (
322
310
  <ChartAggregationOption
@@ -21,6 +21,7 @@ import {
21
21
  useAlertResolveEmergency,
22
22
  useEmergencyButton,
23
23
  } from './hooks/useEmergencyButton';
24
+ import { useDashboardDevice } from './hooks/useDashboardDevice';
24
25
  import { useFavoriteDevice } from './hooks/useFavoriteDevice';
25
26
  import { usePopover } from '../../hooks/Common';
26
27
  import { setConfigGlobalState, getConfigGlobalState } from '../../iot/states';
@@ -172,6 +173,9 @@ const DeviceDetail = ({ route }) => {
172
173
  [display.items]
173
174
  );
174
175
 
176
+ const { isPinnedDashboard, addToDashboard, removeFromDashboard } =
177
+ useDashboardDevice(device);
178
+
175
179
  const { isFavorite, addToFavorites, removeFromFavorites } =
176
180
  useFavoriteDevice(device);
177
181
 
@@ -333,6 +337,30 @@ const DeviceDetail = ({ route }) => {
333
337
 
334
338
  const listMenuItem = useMemo(() => {
335
339
  const menuItems = [];
340
+
341
+ if (!isPinnedDashboard) {
342
+ menuItems.push({
343
+ doAction: addToDashboard,
344
+ text: t('add_to_dashboard'),
345
+ });
346
+ } else {
347
+ menuItems.push({
348
+ doAction: removeFromDashboard,
349
+ text: t('remove_from_dashboard'),
350
+ });
351
+ }
352
+ if (!isFavorite) {
353
+ menuItems.push({
354
+ doAction: addToFavorites,
355
+ text: t('add_to_favorites'),
356
+ });
357
+ } else {
358
+ menuItems.push({
359
+ doAction: removeFromFavorites,
360
+ text: t('remove_from_favorites'),
361
+ });
362
+ }
363
+
336
364
  if (display.items.some((i) => getActionComponent(i.template))) {
337
365
  menuItems.push({
338
366
  route: Routes.ActivityLog,
@@ -443,17 +471,6 @@ const DeviceDetail = ({ route }) => {
443
471
  text: t('passcode_list'),
444
472
  });
445
473
  }
446
- if (!isFavorite) {
447
- menuItems.unshift({
448
- doAction: addToFavorites,
449
- text: t('add_to_favorites'),
450
- });
451
- } else {
452
- menuItems.unshift({
453
- doAction: removeFromFavorites,
454
- text: t('remove_from_favorites'),
455
- });
456
- }
457
474
  return [...menuItems];
458
475
  }, [
459
476
  display,
@@ -462,6 +479,7 @@ const DeviceDetail = ({ route }) => {
462
479
  sideMenu,
463
480
  t,
464
481
  isShowSetUpSmartLock,
482
+ isPinnedDashboard,
465
483
  isFavorite,
466
484
  device,
467
485
  unit,
@@ -471,7 +489,9 @@ const DeviceDetail = ({ route }) => {
471
489
  fetchDataDeviceDetail,
472
490
  isMyUnitDeviceScreen,
473
491
  emergencyDeviceId,
492
+ addToDashboard,
474
493
  addToFavorites,
494
+ removeFromDashboard,
475
495
  removeFromFavorites,
476
496
  ]);
477
497
 
@@ -729,6 +749,19 @@ const DeviceDetail = ({ route }) => {
729
749
  const HeaderRight = useMemo(
730
750
  () => (
731
751
  <View style={styles.headerRight}>
752
+ <TouchableOpacity
753
+ onPress={isPinnedDashboard ? removeFromDashboard : addToDashboard}
754
+ accessibilityLabel={AccessibilityLabel.HEADER_DEVICE_BUTTON_PIN}
755
+ >
756
+ <View style={styles.buttonStar}>
757
+ {isPinnedDashboard ? (
758
+ <IconFill name="pushpin" size={25} color={Colors.Yellow6} />
759
+ ) : (
760
+ <IconOutline name="pushpin" size={25} />
761
+ )}
762
+ </View>
763
+ </TouchableOpacity>
764
+
732
765
  <TouchableOpacity
733
766
  onPress={isFavorite ? removeFromFavorites : addToFavorites}
734
767
  accessibilityLabel={AccessibilityLabel.HEADER_DEVICE_BUTTON_STAR}
@@ -761,12 +794,15 @@ const DeviceDetail = ({ route }) => {
761
794
  </View>
762
795
  ),
763
796
  [
797
+ isPinnedDashboard,
764
798
  isFavorite,
765
- removeFromFavorites,
766
- addToFavorites,
767
799
  isShowSetupEmergencyContact,
768
- onPressSetting,
800
+ addToDashboard,
801
+ addToFavorites,
802
+ removeFromDashboard,
803
+ removeFromFavorites,
769
804
  handleShowMenuAction,
805
+ onPressSetting,
770
806
  ]
771
807
  );
772
808
 
@@ -0,0 +1,34 @@
1
+ import { useContext, useCallback } from 'react';
2
+ import { API } from '../../../configs';
3
+ import { SCContext, useSCContextSelector } from '../../../context';
4
+ import { Action } from '../../../context/actionType';
5
+ import { axiosPost } from '../../../utils/Apis/axios';
6
+
7
+ export const useDashboardDevice = (device) => {
8
+ const { setAction } = useContext(SCContext);
9
+ const dashboardDeviceIds = useSCContextSelector(
10
+ (state) => state.unit.dashboardDeviceIds
11
+ );
12
+
13
+ const isPinnedDashboard = dashboardDeviceIds.includes(device.id);
14
+
15
+ const addToDashboard = useCallback(async () => {
16
+ const { success } = await axiosPost(API.DEVICE.ADD_TO_DASHBOARD(device.id));
17
+ success && setAction(Action.ADD_DEVICES_TO_DASHBOARD, [device.id]);
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
19
+ }, [device.id]);
20
+
21
+ const removeFromDashboard = useCallback(async () => {
22
+ const { success } = await axiosPost(
23
+ API.DEVICE.REMOVE_FROM_DASHBOARD(device.id)
24
+ );
25
+ success && setAction(Action.REMOVE_DEVICES_FROM_DASHBOARD, [device.id]);
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ }, [device.id]);
28
+
29
+ return {
30
+ isPinnedDashboard,
31
+ addToDashboard,
32
+ removeFromDashboard,
33
+ };
34
+ };
@@ -99,4 +99,20 @@ export default StyleSheet.create({
99
99
  marginLeft: 16,
100
100
  marginBottom: 16,
101
101
  },
102
+ widgetLabel: {
103
+ fontSize: 16,
104
+ color: Colors.Black,
105
+ marginHorizontal: 20,
106
+ marginBottom: 4,
107
+ },
108
+ widgetLabelCamera: {
109
+ fontSize: 16,
110
+ color: Colors.Black,
111
+ marginBottom: 4,
112
+ },
113
+ widgetLabelHistory: {
114
+ fontSize: 16,
115
+ color: Colors.Black,
116
+ marginHorizontal: 20,
117
+ },
102
118
  });