@eohjsc/react-native-smart-city 0.3.27 → 0.3.28

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 (67) hide show
  1. package/index.js +2 -0
  2. package/package.json +1 -1
  3. package/src/Images/Common/device_icon.png +0 -0
  4. package/src/Images/DevMode/gateway.png +0 -0
  5. package/src/Images/DevMode/gateway@2x.png +0 -0
  6. package/src/Images/DevMode/gateway@3x.png +0 -0
  7. package/src/Images/DevMode/menu.png +0 -0
  8. package/src/Images/DevMode/menu@2x.png +0 -0
  9. package/src/Images/DevMode/menu@3x.png +0 -0
  10. package/src/Images/DevMode/search.png +0 -0
  11. package/src/Images/DevMode/search@2x.png +0 -0
  12. package/src/Images/DevMode/search@3x.png +0 -0
  13. package/src/Images/DevMode/smart.png +0 -0
  14. package/src/Images/DevMode/smart@2x.png +0 -0
  15. package/src/Images/DevMode/smart@3x.png +0 -0
  16. package/src/Images/DevMode/template.png +0 -0
  17. package/src/Images/DevMode/template@2x.png +0 -0
  18. package/src/Images/DevMode/template@3x.png +0 -0
  19. package/src/commons/CameraDevice/index.js +1 -2
  20. package/src/commons/ConnectingProcess/DeviceItem/DeviceItem.js +20 -12
  21. package/src/commons/ConnectingProcess/DeviceItem/DeviceItemStyles.js +2 -0
  22. package/src/commons/ConnectingProcess/__test__/DeviceItem.test.js +1 -1
  23. package/src/commons/ConnectingProcess/index.js +11 -0
  24. package/src/commons/Dashboard/MyUnit/index.js +1 -1
  25. package/src/commons/DevMode/Label.js +10 -0
  26. package/src/commons/DevMode/Search.js +20 -0
  27. package/src/commons/DevMode/Styles/LabelStyles.js +8 -0
  28. package/src/commons/DevMode/Styles/SearchStyles.js +21 -0
  29. package/src/commons/DevMode/index.js +3 -0
  30. package/src/commons/Form/TextInput.js +4 -0
  31. package/src/commons/MediaPlayerDetail/index.js +0 -20
  32. package/src/commons/Modal/index.js +1 -2
  33. package/src/commons/Tabbar/Styles/indexStyles.js +51 -0
  34. package/src/commons/Tabbar/index.js +110 -0
  35. package/src/configs/Colors.js +4 -0
  36. package/src/configs/Images.js +6 -0
  37. package/src/navigations/GatewayStack.js +23 -0
  38. package/src/navigations/Main.js +144 -0
  39. package/src/navigations/SmartStack.js +23 -0
  40. package/src/navigations/TemplateStack.js +23 -0
  41. package/src/screens/AllCamera/__test__/index.test.js +1 -8
  42. package/src/screens/AllCamera/index.js +0 -13
  43. package/src/screens/Device/hooks/__test__/useEmergencyButton.test.js +37 -0
  44. package/src/screens/Drawer/Drawer.test.js +24 -0
  45. package/src/screens/Drawer/index.js +198 -0
  46. package/src/screens/Gateway/__test__/index.test.js +16 -0
  47. package/src/screens/Gateway/index.js +8 -0
  48. package/src/screens/Smart/__test__/index.test.js +16 -0
  49. package/src/screens/Smart/index.js +8 -0
  50. package/src/screens/SubUnit/AddSubUnit.js +1 -1
  51. package/src/screens/SubUnit/EditSubUnit.js +4 -1
  52. package/src/screens/Template/Styles/indexStyles.js +51 -0
  53. package/src/screens/Template/__test__/index.test.js +16 -0
  54. package/src/screens/Template/index.js +84 -0
  55. package/src/screens/Unit/Detail.js +5 -27
  56. package/src/screens/Unit/Station/__test__/index.test.js +41 -0
  57. package/src/screens/Unit/__test__/Detail.test.js +1 -5
  58. package/src/utils/Converter/__test__/timer.test.js +99 -0
  59. package/src/utils/Functions/Search.js +17 -0
  60. package/src/utils/Functions/ShortEmail.js +4 -0
  61. package/src/utils/Functions/__test__/Search.test.js +6 -0
  62. package/src/utils/Functions/__test__/ShortEmail.test.js +6 -0
  63. package/src/utils/I18n/translations/en.json +11 -1
  64. package/src/utils/I18n/translations/vi.json +11 -1
  65. package/src/utils/Route/index.js +6 -0
  66. package/src/commons/Modal/ModalFullVideo.js +0 -48
  67. package/src/commons/Modal/Styles/ModalFullVideoStyles.js +0 -26
@@ -0,0 +1,84 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ View,
4
+ TouchableWithoutFeedback,
5
+ Keyboard,
6
+ FlatList,
7
+ } from 'react-native';
8
+ import { Label } from '../../commons/DevMode';
9
+ import Search from '../../commons/DevMode/Search';
10
+ import Text from '../../commons/Text';
11
+ import t from '../../hooks/Common/useTranslations';
12
+ import { convertToSlug } from '../../utils/Functions/Search';
13
+ import styles from './Styles/indexStyles';
14
+
15
+ const arrTemplates = [
16
+ {
17
+ id: 1,
18
+ name: 'Template name 1',
19
+ count: 2,
20
+ },
21
+ {
22
+ id: 2,
23
+ name: 'Template name 2',
24
+ },
25
+ {
26
+ id: 3,
27
+ name: 'Template name 3',
28
+ },
29
+ ];
30
+
31
+ const Template = () => {
32
+ const [data, setData] = useState(arrTemplates);
33
+
34
+ const onSearch = useCallback((value) => {
35
+ if (value === '') {
36
+ setData(arrTemplates);
37
+ return;
38
+ }
39
+ const dataTemp = arrTemplates.filter((item) =>
40
+ convertToSlug(item?.name).includes(convertToSlug(value))
41
+ );
42
+ setData(dataTemp);
43
+ }, []);
44
+
45
+ const renderListEmptyComponent = useMemo(() => {
46
+ return (
47
+ <View style={styles.wrapEmpty}>
48
+ <Text style={styles.textEmpty1}>{t('no_template_yet')}</Text>
49
+ <Text style={styles.textEmpty2}>{t('add_your_template')}</Text>
50
+ </View>
51
+ );
52
+ }, []);
53
+
54
+ const renderItem = ({ item, index }) => {
55
+ return (
56
+ <View style={[styles.item, index % 2 === 0 && styles.oddItem]}>
57
+ <Text style={styles.nameItem}>{item?.name}</Text>
58
+ <Text style={styles.countItem}>{`${
59
+ item?.count > 0 ? item?.count : t('no')
60
+ } ${t('gateways').toLowerCase()}`}</Text>
61
+ </View>
62
+ );
63
+ };
64
+
65
+ return (
66
+ <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
67
+ <View style={styles.wrap}>
68
+ <Label name={t('template')} />
69
+ <Search onSearch={onSearch} />
70
+ <FlatList
71
+ contentContainerStyle={styles.contentContainerStyle}
72
+ keyExtractor={(item) => item?.id}
73
+ data={data}
74
+ renderItem={renderItem}
75
+ extraData={data}
76
+ ListEmptyComponent={renderListEmptyComponent}
77
+ numColumns={2}
78
+ />
79
+ </View>
80
+ </TouchableWithoutFeedback>
81
+ );
82
+ };
83
+
84
+ export default Template;
@@ -33,7 +33,6 @@ import WrapParallaxScrollView from '../../commons/WrapParallaxScrollView';
33
33
  import { SCContext, useSCContextSelector } from '../../context';
34
34
  import { Action } from '../../context/actionType';
35
35
  import CameraDevice from '../../commons/CameraDevice';
36
- import { ModalFullVideo } from '../../commons/Modal';
37
36
  import { useNavigation } from '@react-navigation/native';
38
37
  import Routes from '../../utils/Route';
39
38
  import SubUnitAutomate from '../../commons/SubUnit/OneTap';
@@ -63,6 +62,7 @@ const UnitDetail = ({ route }) => {
63
62
  routeName,
64
63
  stationId,
65
64
  isAddSubUnit,
65
+ isEditSubUnit,
66
66
  isSuccessfullyConnected,
67
67
  } = route.params;
68
68
 
@@ -92,8 +92,6 @@ const UnitDetail = ({ route }) => {
92
92
  const [showAdd, setShowAdd, setHideAdd] = useBoolean();
93
93
  const [showPreventAccess, setShowPreventAccess, setHidePreventAccess] =
94
94
  useBoolean(false);
95
- const [isFullScreen, setIsFullScreen] = useState(false);
96
- const [dataFullScreen, setDataFullScreen] = useState();
97
95
  const appState = useRef(AppState.currentState);
98
96
 
99
97
  const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
@@ -105,15 +103,6 @@ const UnitDetail = ({ route }) => {
105
103
  listAutomate
106
104
  );
107
105
 
108
- const handleFullScreen = (data) => {
109
- setIsFullScreen(!isFullScreen);
110
- setDataFullScreen(data);
111
- };
112
-
113
- const onClose = useCallback(() => {
114
- setIsFullScreen(false);
115
- }, []);
116
-
117
106
  const prepareData = useCallback(
118
107
  (rawUnitData) => {
119
108
  rawUnitData.stations.unshift({
@@ -217,8 +206,9 @@ const UnitDetail = ({ route }) => {
217
206
  }, [unit, indexStation]);
218
207
 
219
208
  useEffect(() => {
209
+ isEditSubUnit && setIndexStation(0);
220
210
  isOneTap && setIndexStation(1);
221
- }, [isOneTap]);
211
+ }, [isEditSubUnit, isOneTap]);
222
212
 
223
213
  useEffect(() => {
224
214
  if (listMenuItem.length && isAddSubUnit) {
@@ -227,7 +217,7 @@ const UnitDetail = ({ route }) => {
227
217
  }, [listMenuItem.length, isAddSubUnit]);
228
218
 
229
219
  useEffect(() => {
230
- if (listMenuItem.length && stationId) {
220
+ if (!isEditSubUnit && listMenuItem.length && stationId) {
231
221
  const getStationCurrent = listMenuItem.filter(
232
222
  (item) => item?.station.id === stationId
233
223
  );
@@ -262,13 +252,7 @@ const UnitDetail = ({ route }) => {
262
252
  );
263
253
  }
264
254
  if (station?.camera_devices) {
265
- return (
266
- <CameraDevice
267
- station={station}
268
- handleFullScreen={handleFullScreen}
269
- goToPlayBack={goToPlayBack}
270
- />
271
- );
255
+ return <CameraDevice station={station} goToPlayBack={goToPlayBack} />;
272
256
  } else if (station?.isOneTap) {
273
257
  return (
274
258
  <SubUnitAutomate
@@ -365,12 +349,6 @@ const UnitDetail = ({ route }) => {
365
349
  childRef={childRef}
366
350
  showingPopover={showingPopover}
367
351
  />
368
- <ModalFullVideo
369
- isVisible={isFullScreen}
370
- data={dataFullScreen}
371
- modalStyles={styles.modal}
372
- onClose={onClose}
373
- />
374
352
  <PreventAccess
375
353
  visible={showPreventAccess}
376
354
  hidePreventAccess={setHidePreventAccess}
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { FlatList } from 'react-native';
3
+ import { act, create } from 'react-test-renderer';
4
+ import Station from '..';
5
+ import { SCProvider } from '../../../../context';
6
+ import { mockSCStore } from '../../../../context/mockStore';
7
+
8
+ const mockOnSnapToItem = jest.fn();
9
+
10
+ const wrapComponent = (route) => (
11
+ <SCProvider initState={mockSCStore({})}>
12
+ <Station
13
+ listStation={[{ id: 1, station: { id: 1 }, text: 'station1' }]}
14
+ onSnapToItem={mockOnSnapToItem}
15
+ indexStation={1}
16
+ />
17
+ </SCProvider>
18
+ );
19
+
20
+ describe('Test Station', async () => {
21
+ let tree;
22
+ let route = {
23
+ unitId: 1,
24
+ unitData: {
25
+ id: 1,
26
+ },
27
+ isOneTap: false,
28
+ routeName: 'Test',
29
+ stationId: 1,
30
+ isAddSubUnit: false,
31
+ isSuccessfullyConnected: false,
32
+ };
33
+ it('Test render', async () => {
34
+ await act(async () => {
35
+ tree = create(wrapComponent(route));
36
+ });
37
+ const instance = tree.root;
38
+ const FlatLists = instance.findAllByType(FlatList);
39
+ expect(FlatLists).toHaveLength(1);
40
+ });
41
+ });
@@ -12,7 +12,6 @@ import { TESTID } from '../../../configs/Constants';
12
12
  import { SCProvider } from '../../../context';
13
13
  import { mockSCStore } from '../../../context/mockStore';
14
14
  import CameraDevice from '../../../commons/CameraDevice';
15
- import { ModalFullVideo } from '../../../commons/Modal';
16
15
  import SubUnitFavorites from '../../../commons/SubUnit/Favorites';
17
16
  import api from '../../../utils/Apis/axios';
18
17
  import PreventAccess from '../../../commons/PreventAccess';
@@ -352,11 +351,7 @@ describe('Test UnitDetail', () => {
352
351
  el.props.testID === TESTID.SUB_UNIT_FULL_CAMERA &&
353
352
  el.type === TouchableOpacity
354
353
  );
355
-
356
354
  expect(fullCamera).toHaveLength(0);
357
- const fullView = instance.findAllByType(ModalFullVideo);
358
- expect(fullView).toHaveLength(1);
359
- expect(fullView[0].props.isVisible).toEqual(false);
360
355
  });
361
356
 
362
357
  test('onPress subunit camera devices', async () => {
@@ -438,6 +433,7 @@ describe('Test UnitDetail', () => {
438
433
  };
439
434
  route.params.isAddSubUnit = true;
440
435
  route.params.unitData = unitData;
436
+ route.params.isSuccessfullyConnected = false;
441
437
  await act(async () => {
442
438
  tree = await renderer.create(wrapComponent(route, account));
443
439
  });
@@ -0,0 +1,99 @@
1
+ import { getDateData, getTitleFromTime, timeDifference } from '../time';
2
+
3
+ describe('Test timer', () => {
4
+ let result;
5
+ it('Test timeDifference when elapsed < msPerMinute and symbol=false', async () => {
6
+ result = await timeDifference(
7
+ new Date('2022-08-01T03:24:01'),
8
+ new Date('2022-08-01T03:24:00')
9
+ );
10
+ expect(result).toBe('1 seconds ago');
11
+ });
12
+
13
+ it('Test timeDifference when elapsed < msPerMinute and symbol=true', async () => {
14
+ result = await timeDifference(
15
+ new Date('2022-08-01T03:24:01'),
16
+ new Date('2022-08-01T03:24:00'),
17
+ true
18
+ );
19
+ expect(result).toBe('1 secs ago');
20
+ });
21
+
22
+ it('Test timeDifference when elapsed < msPerHour and symbol=false', async () => {
23
+ result = await timeDifference(
24
+ new Date('2022-08-01T03:24:01'),
25
+ new Date('2022-08-01T03:00:00')
26
+ );
27
+ expect(result).toBe('24 minutes ago');
28
+ });
29
+
30
+ it('Test timeDifference when elapsed < msPerHour and symbol=true', async () => {
31
+ result = await timeDifference(
32
+ new Date('2022-08-01T03:24:01'),
33
+ new Date('2022-08-01T03:00:00'),
34
+ true
35
+ );
36
+ expect(result).toBe('24 mins ago');
37
+ });
38
+
39
+ it('Test timeDifference when elapsed < msPerDay and symbol=false', async () => {
40
+ result = await timeDifference(
41
+ new Date('2022-08-01T03:24:01'),
42
+ new Date('2022-08-01T02:00:00')
43
+ );
44
+ expect(result).toBe('1 hours ago');
45
+ });
46
+
47
+ it('Test timeDifference when elapsed < msPerMonthe', async () => {
48
+ result = await timeDifference(
49
+ new Date('2022-08-01T03:24:01'),
50
+ new Date('2022-07-31T02:00:00')
51
+ );
52
+ expect(result).toBe('1 days ago');
53
+ });
54
+
55
+ it('Test timeDifference when elapsed < msPerYear', async () => {
56
+ result = await timeDifference(
57
+ new Date('2022-08-01T03:24:01'),
58
+ new Date('2022-05-31T02:00:00')
59
+ );
60
+ expect(result).toBe('2 months ago');
61
+ });
62
+
63
+ it('Test timeDifference when elapsed > msPerYear', async () => {
64
+ result = await timeDifference(
65
+ new Date('2022-08-01T03:24:01'),
66
+ new Date('2021-05-31T02:00:00')
67
+ );
68
+ expect(result).toBe('1 years ago');
69
+ });
70
+
71
+ it('Test getTitleFromTime when time = current', async () => {
72
+ result = await getTitleFromTime(new Date(), new Date());
73
+ expect(result).toBe('Today');
74
+ });
75
+
76
+ it('Test getTitleFromTime when time > current', async () => {
77
+ result = await getTitleFromTime(
78
+ new Date('2022-05-31T02:00:00'),
79
+ new Date('2022-05-30T02:00:00')
80
+ );
81
+ expect(result).toBe('31/05/2022');
82
+ });
83
+
84
+ it('Test getTitleFromTime when time < current', async () => {
85
+ result = await getTitleFromTime(
86
+ new Date('2022-05-29T02:00:00'),
87
+ new Date('2022-05-30T02:00:00')
88
+ );
89
+ expect(result).toBe('Yesterday');
90
+ });
91
+
92
+ it('Test getDateData', async () => {
93
+ result = await getDateData(
94
+ new Date('2022-05-30T02:00:00'),
95
+ new Date('2022-06-30T02:00:00')
96
+ );
97
+ expect(result).toEqual([[], 0]);
98
+ });
99
+ });
@@ -0,0 +1,17 @@
1
+ export const formatVietnamese = (str) => {
2
+ str = str.toLowerCase();
3
+ str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
4
+ str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e');
5
+ str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
6
+ str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
7
+ str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
8
+ str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
9
+ str = str.replace(/đ/g, 'd');
10
+ return str;
11
+ };
12
+
13
+ export const convertToSlug = (text) => {
14
+ text = text.substring(0, Math.max(80, text.length));
15
+ text = formatVietnamese(text);
16
+ return text.toLowerCase().replace(/[^\w ]+/g, '');
17
+ };
@@ -0,0 +1,4 @@
1
+ export const shortEmailName = (email) => {
2
+ const regex = '([^@]+)';
3
+ return email?.match(regex)[0] || '';
4
+ };
@@ -0,0 +1,6 @@
1
+ const { convertToSlug } = require('../Search');
2
+
3
+ it('Test Search function', () => {
4
+ const data = convertToSlug('Bệnh viện');
5
+ expect(data).toEqual('benh vien');
6
+ });
@@ -0,0 +1,6 @@
1
+ import { shortEmailName } from '../ShortEmail';
2
+
3
+ it('Test shortEmailName function', () => {
4
+ const data = shortEmailName('emailExample@gmail.com');
5
+ expect(data).toEqual('emailExample');
6
+ });
@@ -1002,5 +1002,15 @@
1002
1002
  "not_activated": "Not activated",
1003
1003
  "open": "Open",
1004
1004
  "close": "Close",
1005
- "create_contact_success": "Create contact success!"
1005
+ "create_contact_success": "Create contact success!",
1006
+ "templates": "Templates",
1007
+ "template": "Template",
1008
+ "gateways": "Gateways",
1009
+ "help": "Help",
1010
+ "about_us": "About us",
1011
+ "exit_dev_mode": "Exit developer mode",
1012
+ "developer_mode": "Developer Mode",
1013
+ "what_are_you_looking_for": "What are you looking for?",
1014
+ "no_template_yet": "No Template yet",
1015
+ "add_your_template": "Add your template at web app"
1006
1016
  }
@@ -1003,5 +1003,15 @@
1003
1003
  "not_activated": "Không kích hoạt",
1004
1004
  "open": "Mở",
1005
1005
  "close": "Đóng",
1006
- "create_contact_success": "Tạo liên hệ thành công!"
1006
+ "create_contact_success": "Tạo liên hệ thành công!",
1007
+ "templates": "Mẫu",
1008
+ "template": "Mẫu",
1009
+ "gateways": "Cổng vào",
1010
+ "help": "Hỗ trợ",
1011
+ "about_us": "Giới thiệu về chúng tôi",
1012
+ "exit_dev_mode": "Thoát khỏi chế độ nhà phát triển",
1013
+ "developer_mode": "Chế độ nhà phát triển",
1014
+ "what_are_you_looking_for": "Bạn đang tìm kiếm cái gì?",
1015
+ "no_template_yet": "Chưa có mẫu nào",
1016
+ "add_your_template": "Thêm mẫu của bạn trên web"
1007
1017
  }
@@ -147,6 +147,12 @@ const Routes = {
147
147
  UnitMemberInformation: 'UnitMemberInformation',
148
148
  EnterPassword: 'EnterPassword',
149
149
  SelectAddToFavorites: 'SelectAddToFavorites',
150
+ Template: 'Template',
151
+ Gateway: 'Gateway',
152
+ Smart: 'Smart',
153
+ TemplateStack: 'TemplateStack',
154
+ GatewayStack: 'GatewayStack',
155
+ SmartStack: 'SmartStack',
150
156
  };
151
157
 
152
158
  export default Routes;
@@ -1,48 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { View, TouchableOpacity, Image } from 'react-native';
3
- import { ModalCustom } from '../../commons/Modal';
4
- import MediaPlayerDetail from '../../commons/MediaPlayerDetail';
5
- import { Images } from '../../configs';
6
- import styles from './Styles/ModalFullVideoStyles';
7
- import { useHiddenStatusBar } from '../../hooks/Common/useStatusBar';
8
-
9
- const ModalFullVideo = ({
10
- animationOut = 'fadeOut',
11
- isVisible = false,
12
- modalStyles,
13
- data,
14
- resizeMode = 'cover',
15
- onClose,
16
- uri,
17
- }) => {
18
- useEffect(() => {
19
- // eslint-disable-next-line react-hooks/rules-of-hooks
20
- useHiddenStatusBar(isVisible);
21
- }, [isVisible]);
22
-
23
- return (
24
- <ModalCustom
25
- animationOut={animationOut}
26
- isVisible={isVisible}
27
- style={modalStyles}
28
- >
29
- <View style={styles.modalContent}>
30
- <MediaPlayerDetail
31
- uri={uri || data?.uri}
32
- thumbnail={data?.thumbnail}
33
- style={styles.camera}
34
- wrapStyles={styles.wrapStyles}
35
- resizeMode={resizeMode}
36
- cameraName={data?.name}
37
- isFullScreen={true}
38
- isPaused={false}
39
- />
40
- <TouchableOpacity onPress={onClose} style={styles.buttonFullScreen}>
41
- <Image source={Images.fullscreen} />
42
- </TouchableOpacity>
43
- </View>
44
- </ModalCustom>
45
- );
46
- };
47
-
48
- export default ModalFullVideo;
@@ -1,26 +0,0 @@
1
- import { StyleSheet } from 'react-native';
2
- import { normalize } from '../../../configs/Constants';
3
-
4
- export default StyleSheet.create({
5
- modalContent: {
6
- position: 'absolute',
7
- zIndex: 10,
8
- top: 0,
9
- left: 0,
10
- right: 0,
11
- bottom: 0,
12
- },
13
- buttonFullScreen: {
14
- position: 'absolute',
15
- zIndex: 10,
16
- width: normalize(40),
17
- height: normalize(40),
18
- bottom: normalize(30),
19
- left: normalize(20),
20
- justifyContent: 'center',
21
- alignItems: 'center',
22
- },
23
- wrapStyles: {
24
- borderRadius: 0,
25
- },
26
- });