@eohjsc/react-native-smart-city 0.3.85 → 0.3.86

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/package.json +3 -2
  2. package/src/Images/Common/logo.png +0 -0
  3. package/src/Images/Common/logo@2x.png +0 -0
  4. package/src/Images/Common/logo@3x.png +0 -0
  5. package/src/Images/Common/unit_default_background.png +0 -0
  6. package/src/Images/Common/unit_default_background@2x.png +0 -0
  7. package/src/Images/Common/unit_default_background@3x.png +0 -0
  8. package/src/commons/ActionGroup/OnOffSmartLock/AutoLock/index.js +1 -1
  9. package/src/commons/ActionGroup/SliderRangeTemplate.js +7 -13
  10. package/src/commons/ActionGroup/SliderRangeTemplateStyles.js +0 -1
  11. package/src/commons/ActionGroup/__test__/SliderRangeTemplate.test.js +4 -4
  12. package/src/commons/ActionGroup/__test__/index.test.js +1 -1
  13. package/src/commons/Automate/__test__/ItemAutomate.test.js +25 -3
  14. package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +37 -0
  15. package/src/commons/Dashboard/MyUnit/index.js +28 -6
  16. package/src/commons/Device/ProgressBar/__test__/ProgressBar.test.js +1 -1
  17. package/src/commons/Device/ProgressBar/index.js +4 -2
  18. package/src/commons/Device/ProgressBar/styles.js +1 -0
  19. package/src/commons/Device/SonosSpeaker/__test__/SonosSpeaker.test.js +1 -1
  20. package/src/commons/Device/SonosSpeaker/index.js +1 -1
  21. package/src/commons/Device/WindSpeed/Anemometer/index.js +96 -14
  22. package/src/commons/Device/WindSpeed/__test__/Anemometer.test.js +89 -2
  23. package/src/commons/ModalPopupCT/index.js +21 -2
  24. package/src/commons/ModalPopupCT/styles.js +7 -0
  25. package/src/commons/WrapParallaxScrollView/index.js +5 -4
  26. package/src/configs/API.js +3 -2
  27. package/src/configs/AccessibilityLabel.js +3 -0
  28. package/src/configs/Constants.js +1 -0
  29. package/src/configs/Images.js +1 -0
  30. package/src/context/actionType.ts +3 -0
  31. package/src/context/mockStore.ts +1 -0
  32. package/src/context/reducer.ts +19 -0
  33. package/src/screens/AddNewAction/__test__/LoadingSelectAction.test.js +16 -0
  34. package/src/screens/AllCamera/__test__/index.test.js +1 -1
  35. package/src/screens/AllCamera/index.js +2 -2
  36. package/src/screens/AllGateway/DetailConfigActionInternal/__test__/index.test.js +179 -111
  37. package/src/screens/AllGateway/DetailConfigActionInternal/index.js +48 -50
  38. package/src/screens/AllGateway/DeviceInternalDetail/__test__/index.test.js +4 -0
  39. package/src/screens/AllGateway/DeviceInternalDetail/index.js +14 -1
  40. package/src/screens/AllGateway/GatewayConnectionMethods/__test__/index.test.js +25 -0
  41. package/src/screens/AllGateway/GatewayConnectionMethods/index.js +59 -35
  42. package/src/screens/AllGateway/GatewayInfo/__test__/index.test.js +14 -4
  43. package/src/screens/AllGateway/GatewayInfo/index.js +21 -2
  44. package/src/screens/AllGateway/components/Detail/__test__/index.test.js +28 -1
  45. package/src/screens/AllGateway/components/Detail/index.js +8 -2
  46. package/src/screens/AllGateway/components/Information/index.js +4 -1
  47. package/src/screens/AllGateway/components/TabPaneCT/__test__/index.test.js +1 -1
  48. package/src/screens/AllGateway/components/TabPaneCT/index.js +1 -1
  49. package/src/screens/AllGateway/components/TabPaneCT/styles.js +1 -1
  50. package/src/screens/ConfirmUnitDeletion/index.js +6 -2
  51. package/src/screens/Unit/ManageUnit.js +7 -3
  52. package/src/utils/I18n/translations/en.json +3 -0
  53. package/src/utils/I18n/translations/vi.json +3 -0
  54. package/src/utils/Utils.js +6 -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.85",
4
+ "version": "0.3.86",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -108,6 +108,7 @@
108
108
  "@formatjs/intl-pluralrules": "^3.4.7",
109
109
  "@invertase/react-native-apple-authentication": "^1.1.2",
110
110
  "@messageformat/core": "^3.0.0",
111
+ "@miblanchard/react-native-slider": "^2.2.0",
111
112
  "@react-native-clipboard/clipboard": "^1.11.1",
112
113
  "@react-native-community/async-storage": "^1.12.1",
113
114
  "@react-native-community/cameraroll": "^4.0.0",
@@ -179,6 +180,7 @@
179
180
  "react-native-maps-directions": "^1.8.0",
180
181
  "react-native-modal": "^11.5.6",
181
182
  "react-native-modal-datetime-picker": "^8.9.3",
183
+ "react-native-new-snap-carousel": "^3.9.3",
182
184
  "react-native-onesignal": "^4.3.1",
183
185
  "react-native-pager-view": "^5.4.1",
184
186
  "react-native-parallax-scroll-view": "^0.21.3",
@@ -190,7 +192,6 @@
190
192
  "react-native-responsive-fontsize": "^0.5.1",
191
193
  "react-native-safe-area-context": "^3.1.1",
192
194
  "react-native-screens": "^2.9.0",
193
- "react-native-snap-carousel": "4.0.0-beta.5",
194
195
  "react-native-super-grid": "^4.0.3",
195
196
  "react-native-svg": "^12.1.0",
196
197
  "react-native-toast-message": "^2.1.1",
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  import React, { memo, useCallback, useState } from 'react';
2
2
  import { View } from 'react-native';
3
- import Slider from '@react-native-community/slider';
3
+ import { Slider } from '@miblanchard/react-native-slider';
4
4
  import Text from '../../../Text';
5
5
  import { HeaderCustom } from '../../../Header';
6
6
  import ButtonWrapper from './ButtonWrapper';
@@ -5,7 +5,7 @@ import styles from './SliderRangeTemplateStyles';
5
5
  import Text from '../Text';
6
6
  import { Colors } from '../../configs';
7
7
  import SvgBrightness from '../../../assets/images/brightness.svg';
8
- import Slider from '@react-native-community/slider';
8
+ import { Slider } from '@miblanchard/react-native-slider';
9
9
  import { useConfigGlobalState } from '../../iot/states';
10
10
  import { DEVICE_TYPE } from '../../configs/Constants';
11
11
 
@@ -17,14 +17,8 @@ const SliderRangeTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
17
17
  const { config = undefined } = configuration;
18
18
 
19
19
  const getPercent = useCallback(() => {
20
- if (config) {
21
- const configValue = configValues[config] || {};
22
- const { value = 0 } = configValue;
23
- if (value) {
24
- return value;
25
- }
26
- }
27
- return 0;
20
+ const { value } = configValues[config] || { value: 0 };
21
+ return value;
28
22
  }, [config, configValues]);
29
23
 
30
24
  const [valueBrightness, setValueBrightness] = useState(getPercent());
@@ -32,9 +26,10 @@ const SliderRangeTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
32
26
 
33
27
  const onChangeBrightness = useCallback(
34
28
  async (value) => {
29
+ const value_brness = value[0];
35
30
  await doAction(
36
31
  configuration?.action_data,
37
- JSON.stringify({ value_brness: value, value: value })
32
+ JSON.stringify({ value_brness: value_brness, value: value_brness })
38
33
  );
39
34
  },
40
35
  [configuration?.action_data, doAction]
@@ -45,8 +40,7 @@ const SliderRangeTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
45
40
  }, [valueBrightness]);
46
41
 
47
42
  useEffect(() => {
48
- const configValue = configValues[config] || {};
49
- const { value = 0 } = configValue;
43
+ const { value } = configValues[config] || { value: 0 };
50
44
  if (value && isFirstTime) {
51
45
  setValueBrightness(value);
52
46
  setIsFirstTime(false);
@@ -71,7 +65,7 @@ const SliderRangeTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
71
65
  </View>
72
66
  <View style={styles.RightBrightness}>
73
67
  <Slider
74
- style={styles.slider}
68
+ containerStyle={styles.slider}
75
69
  value={valueBrightness}
76
70
  onSlidingComplete={onChangeBrightness}
77
71
  onValueChange={setValueBrightness}
@@ -15,7 +15,6 @@ export default StyleSheet.create({
15
15
  },
16
16
  slider: {
17
17
  width: 131,
18
- transform: [{ scaleY: 2 }],
19
18
  },
20
19
  trackSlider: {
21
20
  height: 8,
@@ -5,7 +5,7 @@ import SliderRangeTemplate from '../SliderRangeTemplate';
5
5
  import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
7
  import { useConfigGlobalState } from '../../../iot/states';
8
- import SliderRange from '@react-native-community/slider';
8
+ import { Slider } from '@miblanchard/react-native-slider';
9
9
 
10
10
  jest.mock('../../../iot/Monitor');
11
11
  const mockDoAction = jest.fn();
@@ -55,10 +55,10 @@ describe('Test SliderRangeTemplate', () => {
55
55
  wrapper = await create(wrapComponent(actionGroup, mockDoAction, sensor));
56
56
  });
57
57
  const instance = wrapper.root;
58
- const silderRange = instance.findAllByType(SliderRange);
58
+ const silderRange = instance.findAllByType(Slider);
59
59
  expect(silderRange).toHaveLength(1);
60
60
  await act(async () => {
61
- await silderRange[0].props.onSlidingComplete(50);
61
+ await silderRange[0].props.onSlidingComplete([50]);
62
62
  });
63
63
  expect(mockDoAction).toHaveBeenCalledWith(
64
64
  action_data,
@@ -80,7 +80,7 @@ describe('Test SliderRangeTemplate', () => {
80
80
  wrapper = await create(wrapComponent(actionGroup, mockDoAction, sensor));
81
81
  });
82
82
  const instance = wrapper.root;
83
- const silderRange = instance.findByType(SliderRange);
83
+ const silderRange = instance.findByType(Slider);
84
84
  expect(silderRange.props.value).toBe(50);
85
85
  });
86
86
  });
@@ -14,7 +14,7 @@ import Text from '../../Text';
14
14
  import { AccessibilityLabel } from '../../../configs/Constants';
15
15
  import { SCProvider } from '../../../context';
16
16
  import { mockSCStore } from '../../../context/mockStore';
17
- import Slider from '@react-native-community/slider';
17
+ import { Slider } from '@miblanchard/react-native-slider';
18
18
  import { WheelColorPicker } from '../ColorPickerTemplate';
19
19
  import RadioCircle from '../../RadioCircle';
20
20
 
@@ -1,24 +1,46 @@
1
1
  import React from 'react';
2
2
  import { act, create } from 'react-test-renderer';
3
+ import { TouchableOpacity } from 'react-native';
4
+
3
5
  import { SCProvider } from '../../../context';
4
6
  import { mockSCStore } from '../../../context/mockStore';
5
7
  import Text from '../../Text';
6
8
  import ItemAutomate from '../ItemAutomate';
7
9
 
8
- const wrapComponent = (type = 'one_tap') => (
10
+ const wrapComponent = (rest) => (
9
11
  <SCProvider initState={mockSCStore({})}>
10
- <ItemAutomate type={type} />
12
+ <ItemAutomate {...rest} />
11
13
  </SCProvider>
12
14
  );
13
15
 
14
16
  describe('Test ItemAutomate', () => {
15
17
  let tree;
18
+ let defaultProps = {
19
+ type: 'one_tap',
20
+ };
16
21
  it('Test render', async () => {
17
22
  await act(async () => {
18
- tree = await create(wrapComponent());
23
+ tree = await create(wrapComponent(defaultProps));
19
24
  });
20
25
  const instance = tree.root;
21
26
  const texts = instance.findAllByType(Text);
22
27
  expect(texts[0].props.children).toEqual('Launch One-Tap');
23
28
  });
29
+
30
+ it('Test render TouchableOpacity', async () => {
31
+ const mockOnPress = jest.fn();
32
+ defaultProps = {
33
+ type: 'one_tap',
34
+ onPress: mockOnPress,
35
+ };
36
+ await act(async () => {
37
+ tree = await create(wrapComponent(defaultProps));
38
+ });
39
+ const instance = tree.root;
40
+ const touchableOpacities = instance.findAllByType(TouchableOpacity);
41
+ await act(async () => {
42
+ touchableOpacities[0].props.onPress();
43
+ });
44
+ expect(mockOnPress).toBeCalledTimes(1);
45
+ });
24
46
  });
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
3
  import MockAdapter from 'axios-mock-adapter';
4
+
4
5
  import MyUnit from '..';
5
6
  import MyUnitDevice from '../../../../screens/Unit/components/MyUnitDevice';
6
7
  import { AccessibilityLabel } from '../../../../configs/Constants';
@@ -8,12 +9,27 @@ import { SCProvider } from '../../../../context';
8
9
  import { mockSCStore } from '../../../../context/mockStore';
9
10
  import api from '../../../../utils/Apis/axios';
10
11
  import { API } from '../../../../configs';
12
+ import Routes from '../../../../utils/Route';
13
+ import { Action } from '../../../../context/actionType';
11
14
 
12
15
  const mock = new MockAdapter(api.axiosInstance);
13
16
 
14
17
  const mockedNavigate = jest.fn();
15
18
  const mockedDispatch = jest.fn();
16
19
 
20
+ const mockSetAction = jest.fn();
21
+ jest.mock('react', () => {
22
+ return {
23
+ ...jest.requireActual('react'),
24
+ useContext: () => ({
25
+ stateData: mockSCStore({
26
+ app: { isDeleteUnitSuccessFully: true },
27
+ }),
28
+ setAction: mockSetAction,
29
+ }),
30
+ };
31
+ });
32
+
17
33
  jest.mock('@react-navigation/native', () => {
18
34
  return {
19
35
  ...jest.requireActual('@react-navigation/native'),
@@ -104,5 +120,26 @@ describe('Test MyUnit', () => {
104
120
  const instance = tree.root;
105
121
  const devices = instance.findAllByType(MyUnitDevice);
106
122
  expect(devices).toHaveLength(2);
123
+
124
+ const button = instance.findByProps({
125
+ accessibilityLabel: `${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-0`,
126
+ });
127
+ await act(async () => {
128
+ await button.props.onPress(data[0]);
129
+ });
130
+ expect(mockedNavigate).toBeCalledWith(Routes.UnitStack, {
131
+ params: { unitId: 1 },
132
+ screen: Routes.UnitDetail,
133
+ });
134
+ });
135
+
136
+ it('Test isDeleteUnitSuccessFully', async () => {
137
+ jest.useFakeTimers();
138
+ mock.onGet(API.UNIT.MY_UNITS()).replyOnce(200, data);
139
+ await act(async () => {
140
+ tree = await renderer.create(wrapComponent());
141
+ });
142
+ jest.runAllTimers();
143
+ expect(mockSetAction).toBeCalledWith(Action.RESET_DELETE_UNIT_ACTION);
107
144
  });
108
145
  });
@@ -5,8 +5,9 @@ import React, {
5
5
  useState,
6
6
  useContext,
7
7
  useMemo,
8
+ useRef,
8
9
  } from 'react';
9
- import { View, Image, TouchableOpacity, Dimensions } from 'react-native';
10
+ import { View, TouchableOpacity, Dimensions } from 'react-native';
10
11
  import {
11
12
  useNavigation,
12
13
  useIsFocused,
@@ -14,35 +15,41 @@ import {
14
15
  } from '@react-navigation/native';
15
16
  import NetInfo from '@react-native-community/netinfo';
16
17
  import { BleManager } from 'react-native-ble-plx';
17
- import Carousel from 'react-native-snap-carousel';
18
-
19
18
  import { API, Colors, Images } from '../../../configs';
20
19
  import Text from '../../Text';
21
20
  import { fetchWithCache } from '../../../utils/Apis/axios';
21
+
22
22
  import styles from './styles';
23
23
  import { Section } from '../../Section';
24
24
  import { useTranslations } from '../../../hooks/Common/useTranslations';
25
25
  import { useUnitConnectRemoteDevices } from '../../../screens/Unit/hook/useUnitConnectRemoteDevices';
26
26
  import { useWatchConfigs, useBluetoothConnection } from '../../../hooks/IoT';
27
- import { SCContext } from '../../../context';
27
+ import { SCContext, useSCContextSelector } from '../../../context';
28
28
  import { Action } from '../../../context/actionType';
29
+
30
+ import Carousel from 'react-native-new-snap-carousel';
29
31
  import { AccessibilityLabel, DEVICE_TYPE } from '../../../configs/Constants';
30
32
  import Routes from '../../../utils/Route';
31
33
  import MyUnitDevice from '../../../screens/Unit/components/MyUnitDevice';
32
34
  import { STORAGE_KEY } from '../../../utils/Storage';
33
35
  import { preloadImagesFromUnits } from '../../../utils/Functions/preloadImages';
36
+ import FImage from '../../FImage';
34
37
 
35
38
  let screenWidth = Dimensions.get('window').width;
36
39
 
37
40
  const bleManager = new BleManager();
38
41
 
39
42
  const MyUnit = ({ refreshing }) => {
43
+ const carouselRef = useRef();
40
44
  const t = useTranslations();
41
45
  const isFocused = useIsFocused();
42
46
  const navigation = useNavigation();
43
47
  const [myUnits, setMyUnits] = useState([]);
44
48
  const [slideIndex, setSlideIndex] = useState(0);
45
49
  const { setAction } = useContext(SCContext);
50
+ const isDeleteUnitSuccessFully = useSCContextSelector(
51
+ (state) => state.app.isDeleteUnitSuccessFully
52
+ );
46
53
 
47
54
  const {
48
55
  permissionsRequested: bluetoothPermRequested,
@@ -136,9 +143,13 @@ const MyUnit = ({ refreshing }) => {
136
143
  accessibilityLabel={`${AccessibilityLabel.MY_UNIT_GO_TO_DETAIL}-${index}`}
137
144
  >
138
145
  <View style={styles.overlay} />
139
- <Image
146
+ <FImage
140
147
  style={styles.bgMyUnit}
141
- source={{ uri: item.background }}
148
+ source={
149
+ item?.background
150
+ ? { uri: item.background }
151
+ : Images.unitDefaultBackground
152
+ }
142
153
  defaultSource={Images.BgUnit}
143
154
  resizeMode="cover"
144
155
  />
@@ -158,6 +169,16 @@ const MyUnit = ({ refreshing }) => {
158
169
  preloadImagesFromUnits(myUnits, STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_UNITS);
159
170
  }, [myUnits]);
160
171
 
172
+ useEffect(() => {
173
+ if (isDeleteUnitSuccessFully) {
174
+ const to = setTimeout(() => {
175
+ carouselRef?.current?.snapToItem();
176
+ setAction(Action.RESET_DELETE_UNIT_ACTION);
177
+ }, 60);
178
+ return () => clearTimeout(to);
179
+ }
180
+ }, [isDeleteUnitSuccessFully, setAction]);
181
+
161
182
  return (
162
183
  <>
163
184
  <Section style={styles.boxTxtMyUnit}>
@@ -167,6 +188,7 @@ const MyUnit = ({ refreshing }) => {
167
188
  <View style={styles.container}>
168
189
  {myUnits.length ? (
169
190
  <Carousel
191
+ ref={carouselRef}
170
192
  layout={'default'}
171
193
  data={myUnits}
172
194
  sliderWidth={screenWidth}
@@ -44,6 +44,6 @@ describe('Test ProgressBar', () => {
44
44
  const texts = instance.findAllByType(Text);
45
45
  expect(texts[0].props.children).toBe('Value bar');
46
46
  expect(texts[1].props.children).toEqual(['Max value', ': ', 100]);
47
- expect(texts[2].props.children).toEqual([10, '%']);
47
+ expect(texts[2].props.children).toEqual([10, ' ', undefined]);
48
48
  });
49
49
  });
@@ -13,7 +13,7 @@ const ProgressBar = memo(({ data = [], item }) => {
13
13
  const maxValue = useMemo(() => {
14
14
  return Number(item?.configuration?.max_value) || 60;
15
15
  }, [item?.configuration?.max_value]);
16
- const value = data.length ? data[0].value : 0;
16
+ const { value = 0, measure, unit } = data.length ? data[0] : {};
17
17
  const percent = value / maxValue; // a number between 0 and 1
18
18
 
19
19
  return (
@@ -36,7 +36,9 @@ const ProgressBar = memo(({ data = [], item }) => {
36
36
  borderWidth={0}
37
37
  borderRadius={10}
38
38
  />
39
- <Text style={styles.textValue}>{value}%</Text>
39
+ <Text style={styles.textValue}>
40
+ {value} {unit || measure}
41
+ </Text>
40
42
  </View>
41
43
  </View>
42
44
  );
@@ -12,6 +12,7 @@ export default StyleSheet.create({
12
12
  padding: 12,
13
13
  borderRadius: 8,
14
14
  borderColor: Colors.Gray4,
15
+ marginBottom: 12,
15
16
  },
16
17
  textLabel: {
17
18
  paddingHorizontal: 16,
@@ -6,7 +6,7 @@ import { mockSCStore } from '../../../../context/mockStore';
6
6
  import Text from '../../../Text';
7
7
  import SonosSpeaker from '..';
8
8
  import { TouchableOpacity } from 'react-native';
9
- import Slider from '@react-native-community/slider';
9
+ import { Slider } from '@miblanchard/react-native-slider';
10
10
 
11
11
  const wrapComponent = () => (
12
12
  <SCProvider initState={mockSCStore({})}>
@@ -2,7 +2,7 @@ import React, { memo, useState } from 'react';
2
2
  import { View, TouchableOpacity } from 'react-native';
3
3
  import { useTranslations } from '../../../hooks/Common/useTranslations';
4
4
  import { Icon } from '@ant-design/react-native';
5
- import Slider from '@react-native-community/slider';
5
+ import { Slider } from '@miblanchard/react-native-slider';
6
6
 
7
7
  import Text from '../../Text';
8
8
  import { Colors } from '../../../configs';
@@ -1,12 +1,13 @@
1
- import React, { memo, useCallback, useMemo } from 'react';
1
+ import React, { memo, useCallback, useMemo, useState } from 'react';
2
2
  import Svg, { Path, Text, Circle } from 'react-native-svg';
3
- import { View, StyleSheet } from 'react-native';
3
+ import { View, StyleSheet, Text as ReactText } from 'react-native';
4
4
 
5
5
  import {
6
6
  drawArc,
7
7
  polarToCartesian,
8
8
  } from '../../../UnitSummary/AirQuality/SegmentedRoundDisplay/helper';
9
9
  import { Colors, Fonts } from '../../../../configs';
10
+ import AccessibilityLabel from '../../../../configs/AccessibilityLabel';
10
11
 
11
12
  const { PI } = Math;
12
13
 
@@ -16,12 +17,13 @@ const Anemometer = memo(
16
17
  width = 240,
17
18
  size = 170,
18
19
  strokeWidth = 16,
19
- numberOfSection = 4,
20
+ numberOfSection = 2,
20
21
  startAngle = -45,
21
22
  endAngle = 225,
22
23
  txtColor = Colors.Gray8,
23
24
  item,
24
25
  }) => {
26
+ const [isShowToolTip, setIsShowToolTip] = useState(false);
25
27
  const center = {
26
28
  x: size / 2,
27
29
  y: size / 2,
@@ -121,6 +123,38 @@ const Anemometer = memo(
121
123
  txtColor,
122
124
  ]);
123
125
 
126
+ // eslint-disable-next-line no-shadow
127
+ const handleShowToolTip = (value) => () => setIsShowToolTip(value);
128
+
129
+ const valueSize = useMemo(() => {
130
+ if ([null, undefined, NaN].includes(value)) {
131
+ return;
132
+ }
133
+ if (value.toString().length < 4) {
134
+ return [56, 16];
135
+ }
136
+ if (value.toString().length < 7) {
137
+ return [35, 10];
138
+ }
139
+ if (value.toString().length < 10) {
140
+ return [26, 6];
141
+ }
142
+ return [20, 2];
143
+ }, [value]);
144
+
145
+ const toolTipTranslateX = useMemo(() => {
146
+ if (value.toString().length < 4) {
147
+ return -20;
148
+ }
149
+ if (value.toString().length < 6) {
150
+ return -30;
151
+ }
152
+ if (value.toString().length < 8) {
153
+ return -40;
154
+ }
155
+ return -50;
156
+ }, [value]);
157
+
124
158
  return (
125
159
  <View style={styles.standard}>
126
160
  <View style={styles.wrapOffset}>
@@ -137,6 +171,41 @@ const Anemometer = memo(
137
171
  />
138
172
  </Svg>
139
173
  </View>
174
+
175
+ <View style={styles.textValue}>
176
+ <Svg width={width} height={width} {...viewBox}>
177
+ <Text
178
+ fill={Colors.Lime6}
179
+ fontSize={valueSize[0]}
180
+ fontWeight="bold"
181
+ x={width / 2}
182
+ y={width / 2 + valueSize[1]}
183
+ textAnchor="middle"
184
+ fontFamily={Fonts.Regular}
185
+ onPressIn={handleShowToolTip(true)}
186
+ onPressOut={handleShowToolTip(false)}
187
+ >
188
+ {value.toString().length > 10
189
+ ? value.toString().substring(0, 10) + '...'
190
+ : value}
191
+ </Text>
192
+ </Svg>
193
+ {isShowToolTip && (
194
+ <View
195
+ style={{
196
+ ...styles.toolTip,
197
+ top: width / 2 - 50,
198
+ left: width / 2,
199
+ transform: [{ translateX: toolTipTranslateX }],
200
+ }}
201
+ accessibilityLabel={AccessibilityLabel.WRAP_TOOL_TIP}
202
+ >
203
+ <ReactText style={styles.textToolTip}>{value}</ReactText>
204
+ <View style={styles.triangle} />
205
+ </View>
206
+ )}
207
+ </View>
208
+
140
209
  <Svg width={width} height={width} {...viewBox}>
141
210
  <Path
142
211
  x={(width - size) / 2}
@@ -147,17 +216,6 @@ const Anemometer = memo(
147
216
  {...{ d, strokeWidth }}
148
217
  />
149
218
  {textAngles()}
150
- <Text
151
- fill={Colors.Lime6}
152
- fontSize={48}
153
- fontWeight="bold"
154
- x={width / 2}
155
- y={width / 2 + 16}
156
- textAnchor="middle"
157
- fontFamily={Fonts.Regular}
158
- >
159
- {value}
160
- </Text>
161
219
  <Text
162
220
  fill={Colors.Gray8}
163
221
  fontSize={16}
@@ -186,4 +244,28 @@ const styles = StyleSheet.create({
186
244
  position: 'absolute',
187
245
  zIndex: 2,
188
246
  },
247
+ textValue: {
248
+ position: 'absolute',
249
+ zIndex: 4,
250
+ },
251
+ toolTip: {
252
+ position: 'absolute',
253
+ paddingHorizontal: 14,
254
+ backgroundColor: Colors.Black,
255
+ justifyContent: 'center',
256
+ alignItems: 'center',
257
+ borderRadius: 20,
258
+ },
259
+ textToolTip: {
260
+ color: Colors.White,
261
+ },
262
+ triangle: {
263
+ position: 'absolute',
264
+ bottom: -6,
265
+ borderTopWidth: 6,
266
+ borderRightWidth: 4,
267
+ borderLeftWidth: 4,
268
+ borderRightColor: Colors.TextTransparent,
269
+ borderLeftColor: Colors.TextTransparent,
270
+ },
189
271
  });