Charging.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. /**
  2. * 充电中的页面组件
  3. * @邠心vbe on 2021/04/13
  4. */
  5. import React, { useRef, useEffect, useState } from 'react';
  6. import { Animated, View, Easing, StyleSheet, Image, Text, ImageBackground, TextInput } from 'react-native';
  7. import { RadialGradient } from 'react-native-gradients';
  8. import Modal from 'react-native-modal';
  9. import { ModalProps } from '../../components/BottomModal';
  10. import Button, { ElevationObject, ViewHeight } from '../../components/Button';
  11. import ChargeItemSelect from '../../icons/ChargeItemSelect';
  12. import { DialogMaxWidth } from './InfoDialog';
  13. import { QRResult } from '../charge/QRScan';
  14. import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
  15. import utils from '../../utils/utils';
  16. export const circleSize = $vw(50.66) < 300 ? $vw(50.66) : 300;
  17. const batterySize = 0.463 * circleSize;
  18. const batteryWidth = 0.659*batterySize;
  19. export const TypeImage = {
  20. AC: require('../../images/charge/ic-type-ac.png'),
  21. DC: require('../../images/charge/ic-type-dc.png'),
  22. CHADEMO: require('../../images/charge/ic-type-chademo.png')
  23. }
  24. export const TypeImageList = [
  25. {
  26. name: 'AC',
  27. key: 'AC',
  28. icon: require('../../images/charge/ic-type-ac.png')
  29. }, {
  30. name: 'DC',
  31. key: 'DC',
  32. icon: require('../../images/charge/ic-type-dc.png')
  33. }, /*{
  34. name: 'Chademo',
  35. key: 'CHADEMO',
  36. icon: require('../../images/charge/ic-type-chademo.png')
  37. }*/
  38. ]
  39. export const getConnectTypeByKey = (key) => {
  40. for (let item of TypeImageList) {
  41. if (item.key == key) {
  42. return item;
  43. }
  44. }
  45. }
  46. export const CircleAnimate = ({isStart = false}) => {
  47. var rotate = useRef(new Animated.Value(0)).current;
  48. const spins = () => {
  49. Animated.timing(rotate, {
  50. toValue: 1,
  51. duration: 10000,
  52. easing: Easing.linear,
  53. useNativeDriver: true
  54. }).start(() => {
  55. rotate.setValue(0);
  56. spins();
  57. });
  58. }
  59. useEffect(() => {
  60. if (isStart) {
  61. spins();
  62. }
  63. }, [rotate]);
  64. const spin = rotate.interpolate({
  65. inputRange: [0, 1],
  66. outputRange: ['0deg', '360deg']
  67. })
  68. return (
  69. <Animated.View
  70. style={[
  71. styles.chargeCircle, {
  72. transform: [{
  73. rotate: spin
  74. }]
  75. }
  76. ]}>
  77. { isStart &&
  78. <View style={{ width: 25, height: 25, marginTop: -11}}>
  79. <RadialGradient
  80. x="50%" y="50%" rx="50%" ry="50%"
  81. colorList={[
  82. {offset: '0%', color: '#FFF', opacity: .8},
  83. {offset: '40%', color: '#FFF', opacity: .5},
  84. {offset: '70%', color: '#FFF', opacity: .3},
  85. {offset: '100%', color: '#FFF',opacity: 0}
  86. ]}/>
  87. </View>
  88. }
  89. </Animated.View>
  90. );
  91. }
  92. export const BatteryView = ({soc, isPending, isCharging}) => {
  93. var [powerPercent, setPercent] = useState(-1);
  94. /*var [powerText, setText] = useState(0);
  95. const autoCharge = () => {
  96. setTimeout(() => {
  97. const p = powerPercent + 0.001
  98. setPercent(Number(p.toFixed(4)))
  99. setText((powerPercent * 100).toFixed(0))
  100. if (powerPercent >= 1) {
  101. onComplete();
  102. }
  103. }, 50 + powerPercent * 100);
  104. }
  105. useEffect(() => {
  106. if (run && powerPercent <= 1) {
  107. autoCharge();
  108. }
  109. }, [powerPercent])*/
  110. useEffect(() => {
  111. if (isCharging) {
  112. try {
  113. var s = '-1' + soc;
  114. var d = '' + parseInt(s);
  115. d = d.replace('-1', '');
  116. if (d) {
  117. setPercent(parseInt(d))
  118. } else {
  119. setPercent(-1);
  120. }
  121. } catch (e) {
  122. setPercent(-1);
  123. }
  124. }
  125. }, [soc]);
  126. const getOpacity = (unit) => {
  127. var op = 1 * (powerPercent + unit);
  128. return op < 1 ? op : 1;
  129. }
  130. const getHeight = () => {
  131. if (powerPercent > 1) {
  132. return batterySize * powerPercent / 100;
  133. } else if (powerPercent > 0) {
  134. return batterySize * powerPercent;
  135. } else {
  136. return 0
  137. }
  138. }
  139. return (
  140. isCharging
  141. ? <View style={ui.center}>
  142. <ImageBackground
  143. style={{
  144. width: circleSize,
  145. height: circleSize,
  146. margin: 32,
  147. padding: 32,
  148. alignItems: 'center',
  149. justifyContent: 'center'
  150. }}
  151. source={require('../../images/charge/ic-charge-circle.png')}>
  152. <View style={styles.chargingView}>
  153. <View style={styles.plusLeftView}>
  154. <Image
  155. style={[styles.plusMiddle, { opacity: getOpacity(0.5)}]}
  156. source={require('../../images/charge/ic-plus-middle.png')}/>
  157. <Image
  158. style={[styles.plusSmall, { opacity: getOpacity(0.4)}]}
  159. source={require('../../images/charge/ic-plus-small.png')}/>
  160. </View>
  161. <View style={styles.batteryView}>
  162. <View style={[styles.batteryIcon]}>
  163. <Image
  164. style={styles.batteryIcon}
  165. source={require('../../images/charge/ic-battery-0.png')}/>
  166. <View style={[styles.batteryLayer, { height: getHeight() }]}>
  167. <Image
  168. style={[styles.batteryIcon]}
  169. source={require('../../images/charge/ic-battery-1.png')}/>
  170. </View>
  171. </View>
  172. { isPending
  173. ? <Text style={styles.batterySoc}>Initiating...</Text>
  174. : powerPercent != -1
  175. ? <Text style={styles.batteryPercent}>{powerPercent}%</Text>
  176. : <Text style={styles.batterySoc}>{'In Charging'}</Text>
  177. }
  178. </View>
  179. <View style={styles.plusRightView}>
  180. <Image
  181. style={[styles.plusLarge, { opacity: getOpacity(0.6)}]}
  182. source={require('../../images/charge/ic-plus-large.png')}/>
  183. </View>
  184. </View>
  185. <CircleAnimate isStart={true}/>
  186. </ImageBackground>
  187. </View>
  188. : <View style={styles.completeView}>
  189. <Image
  190. style={styles.disconnectIcon}
  191. source={require('../../images/charge/charge-complete.png')}/>
  192. <Text style={styles.completeTip}>Please disconnect and return connector to charging station</Text>
  193. </View>
  194. );
  195. }
  196. export const DashboardView = ({isCharging=false, connectorInfo={}}) => {
  197. if (isCharging) {
  198. return (
  199. <View style={styles.dashboardStartView}>
  200. <View style={styles.dashboardGradientView}>
  201. <Svg width="365" height="132" viewBox="0 0 365 132">
  202. <Rect width="365" height="132" fill="url(#paint0_linear_4415_6112)"/>
  203. <Defs>
  204. <LinearGradient id="paint0_linear_4415_6112" x1="169.5" y1="136.5" x2="170.5" y2="5.49999" gradientUnits="userSpaceOnUse">
  205. <Stop stopColor="#5BFA00"/>
  206. <Stop offset="1" stopColor="#009E81"/>
  207. </LinearGradient>
  208. </Defs>
  209. </Svg>
  210. </View>
  211. <Text style={styles.dashboardTitleWhite}>Charging In Progress...</Text>
  212. <View style={styles.dashboardItemGroup}>
  213. <View style={styles.dashboardItemView}>
  214. <Text style={styles.dashboardItemValue}>{utils.minutes2HHmm(connectorInfo?.timeElapsed ?? 0)}</Text>
  215. <Text
  216. numberOfLines={1}
  217. ellipsizeMode="tail"
  218. style={styles.dashboardItemTitle}>Time Elapsed</Text>
  219. </View>
  220. <View style={styles.dashboardItemView}>
  221. <Text style={styles.dashboardItemValue}>{utils.toFixed(connectorInfo?.totalKWhDelivered, 2) ?? "0"} kWh</Text>
  222. <Text
  223. numberOfLines={1}
  224. ellipsizeMode="tail"
  225. style={styles.dashboardItemTitle}>Total kWh Delivered</Text>
  226. </View>
  227. <View style={styles.dashboardItemView}>
  228. <Text style={styles.dashboardItemValueWeight}>{currency} {utils.toFixed(connectorInfo?.totalCharges, 2) ?? "0.0"}</Text>
  229. <Text
  230. numberOfLines={1}
  231. ellipsizeMode="tail"
  232. style={styles.dashboardItemTitle}>Total Charges</Text>
  233. </View>
  234. </View>
  235. </View>
  236. )
  237. } else {
  238. return (
  239. <View style={styles.dashboardGreyView}>
  240. <Text style={styles.dashboardTitle}>Press Start To Begin</Text>
  241. <View style={styles.dashboardItemGroup}>
  242. <View style={styles.dashboardItemView}>
  243. <Text style={styles.dashboardItemValue}>-</Text>
  244. <Text
  245. numberOfLines={1}
  246. ellipsizeMode="tail"
  247. style={styles.dashboardItemTitle}>Time Elapsed</Text>
  248. </View>
  249. <View style={styles.dashboardItemView}>
  250. <Text style={styles.dashboardItemValue}>-</Text>
  251. <Text
  252. numberOfLines={1}
  253. ellipsizeMode="tail"
  254. style={styles.dashboardItemTitle}>Total kWh Delivered</Text>
  255. </View>
  256. <View style={styles.dashboardItemView}>
  257. <Text style={styles.dashboardItemValueWeight}>-</Text>
  258. <Text
  259. numberOfLines={1}
  260. ellipsizeMode="tail"
  261. style={styles.dashboardItemTitle}>Total Charges</Text>
  262. </View>
  263. </View>
  264. </View>
  265. )
  266. }
  267. }
  268. export const EnterStationDialog = ({visible, stationId, onConfirm, onClose}) => {
  269. var [inputStationId, setInput] = useState('')
  270. var [iosKillDialog, killDialog] = useState(true)
  271. const enterStatioinId= () => {
  272. //console.log(inputStationId);
  273. if (inputStationId) {
  274. if (isIOS && iosKillDialog) {
  275. onClose();
  276. killDialog(false);
  277. } else {
  278. QRResult.applyInputStation(inputStationId, stationId, (success, err) => {
  279. setInput('')
  280. if (success) {
  281. if (onConfirm) onConfirm()
  282. } else if (err) {
  283. toastShort(err)
  284. }
  285. if (onClose) onClose()
  286. });
  287. }
  288. } else {
  289. toastShort('Please input Station ID')
  290. }
  291. }
  292. useEffect(() => {
  293. if (!visible && !iosKillDialog) {
  294. killDialog(true);
  295. setTimeout(() => {
  296. enterStatioinId();
  297. }, 100);
  298. }
  299. }, [iosKillDialog])
  300. return (
  301. iosKillDialog
  302. ? <Modal
  303. isVisible={visible}
  304. {...ModalProps}
  305. onBackdropPress={() => onClose}
  306. onBackButtonPress={() => onClose}>
  307. <View style={styles.stationDialog}>
  308. <Text style={styles.stationInputTitle}>Enter Station ID</Text>
  309. <TextInput
  310. style={styles.stationInput}
  311. defaultValue={inputStationId}
  312. placeholder='e.g: LEMOC0002-1'
  313. placeholderTextColor={textPlacehoder}
  314. onChangeText={text => setInput(text)}
  315. />
  316. <View style={styles.dialogButtons}>
  317. <Button
  318. textSize={17}
  319. style={styles.buttonCancel}
  320. viewStyle={ViewHeight(42)}
  321. text='Close'
  322. textColor={textCancel}
  323. onClick={onClose}/>
  324. <Button
  325. textSize={17}
  326. style={styles.buttonOK}
  327. viewStyle={ViewHeight(42)}
  328. text='Confirm'
  329. onClick={() => enterStatioinId()}/>
  330. </View>
  331. </View>
  332. </Modal>
  333. : <></>
  334. )
  335. }
  336. const styles = StyleSheet.create({
  337. chargingView: {
  338. width: circleSize,
  339. height: circleSize,
  340. flexDirection: 'row',
  341. alignItems: 'center'
  342. },
  343. chargeCircle: {
  344. top: 0,
  345. left: 0,
  346. zIndex: 10,
  347. width: circleSize,
  348. height: circleSize,
  349. position: 'absolute',
  350. alignItems: 'center',
  351. borderRadius: circleSize
  352. },
  353. batteryView: {
  354. paddingTop: 8,
  355. paddingLeft: 12,
  356. paddingRight: 12,
  357. alignItems: 'center',
  358. justifyContent: 'center'
  359. },
  360. batteryIcon: {
  361. width: batteryWidth,
  362. height: batterySize,
  363. position: 'relative',
  364. justifyContent: 'flex-end',
  365. },
  366. batteryLayer: {
  367. left: 0,
  368. right: 0,
  369. height: 0,
  370. width: batteryWidth,
  371. overflow: 'hidden',
  372. position: 'absolute',
  373. justifyContent: 'flex-end',
  374. },
  375. batteryPercent: {
  376. color: textPrimary,
  377. fontSize: 22,
  378. textAlign: 'center',
  379. paddingTop: 10,
  380. paddingBottom: 8
  381. },
  382. batterySoc: {
  383. color: textPrimary,
  384. fontSize: 18,
  385. textAlign: 'center',
  386. paddingTop: 10,
  387. paddingBottom: 10
  388. },
  389. plusLeftView: {
  390. flex: 1,
  391. height: batterySize + 10,
  392. marginBottom: 12,
  393. alignItems: 'flex-end',
  394. justifyContent: 'space-around'
  395. },
  396. plusRightView: {
  397. flex: 1,
  398. justifyContent: 'center'
  399. },
  400. plusLarge: {
  401. width: 24,
  402. height: 24
  403. },
  404. plusMiddle: {
  405. width: 18,
  406. height: 18
  407. },
  408. plusSmall: {
  409. width: 12,
  410. height: 12,
  411. marginBottom: 8
  412. },
  413. selectView: {
  414. position: 'relative'
  415. },
  416. selectIcon: {
  417. width: 18,
  418. height: 18
  419. },
  420. selectIconAbs: {
  421. top: -2,
  422. right: -4,
  423. zIndex: 2,
  424. position: 'absolute'
  425. },
  426. completeView: {
  427. height: circleSize,
  428. marginBottom: 16,
  429. alignItems: 'center',
  430. justifyContent: 'center'
  431. },
  432. disconnectIcon: {
  433. width: 100,
  434. height: 100
  435. },
  436. completeTip: {
  437. width: circleSize,
  438. color: textPrimary,
  439. fontSize: 16,
  440. paddingLeft: 16,
  441. paddingRight: 16,
  442. textAlign: 'center'
  443. },
  444. stationDialog: {
  445. padding: 16,
  446. marginLeft: 'auto',
  447. marginRight: 'auto',
  448. borderRadius: isIOS ? 20 : 3,
  449. width: DialogMaxWidth,
  450. backgroundColor: colorLight
  451. },
  452. stationInput: {
  453. ...$padding(4, 10),
  454. minHeight: 40,
  455. borderRadius: 3,
  456. backgroundColor: '#F0F0F0'
  457. },
  458. stationInputTitle: {
  459. fontSize: 15,
  460. textAlign: 'center',
  461. paddingBottom: 16
  462. },
  463. dialogButtons: {
  464. paddingTop: 24,
  465. paddingBottom: 8,
  466. flexDirection: 'row'
  467. },
  468. buttonOK: {
  469. flex: 1,
  470. marginLeft: 12,
  471. },
  472. buttonCancel: {
  473. flex: 1,
  474. borderWidth: 1,
  475. borderColor: colorCancel,
  476. backgroundColor: pageBackground
  477. },
  478. dashboardGreyView: {
  479. padding: 16,
  480. marginTop: 16,
  481. marginBottom: 16,
  482. borderRadius: 6,
  483. ...ElevationObject(5),
  484. backgroundColor: pageBackground
  485. },
  486. dashboardStartView: {
  487. padding: 16,
  488. marginTop: 16,
  489. marginBottom: 16,
  490. borderRadius: 6,
  491. ...ElevationObject(5)
  492. },
  493. dashboardGradientView: {
  494. top: 0,
  495. left: 0,
  496. right: 0,
  497. bottom: 0,
  498. borderRadius: 6,
  499. overflow: 'hidden',
  500. position: "absolute",
  501. },
  502. dashboardTitle: {
  503. color: textPrimary,
  504. fontSize: 25,
  505. fontWeight: 'bold',
  506. textAlign: 'center',
  507. paddingBottom: 16
  508. },
  509. dashboardTitleWhite: {
  510. color: textLight,
  511. fontSize: 25,
  512. fontWeight: 'bold',
  513. textAlign: 'center',
  514. paddingBottom: 16
  515. },
  516. dashboardItemGroup: {
  517. marginRight: -12,
  518. marginBottom: -32,
  519. alignItems: 'center',
  520. flexDirection: 'row'
  521. },
  522. dashboardItemView: {
  523. flex: 1,
  524. marginRight: 12,
  525. borderRadius: 6,
  526. ...$padding(10, 6, 14),
  527. ...ElevationObject(5),
  528. backgroundColor: colorLight
  529. },
  530. dashboardItemTitle: {
  531. color: textCancel,
  532. fontSize: 12,
  533. textAlign: 'center',
  534. paddingTop: 2
  535. },
  536. dashboardItemValue: {
  537. color: textPrimary,
  538. fontSize: 14,
  539. textAlign: 'center'
  540. },
  541. dashboardItemValueWeight: {
  542. color: textPrimary,
  543. fontSize: 14,
  544. fontWeight: 'bold',
  545. textAlign: 'center'
  546. }
  547. });
  548. export const SelectableIcon = ({selected, children}) => {
  549. return (
  550. <View style={styles.selectView}>
  551. {selected &&
  552. <View style={[styles.selectIcon, children && styles.selectIconAbs]}>
  553. <ChargeItemSelect size={18} selected={true}/>
  554. </View>
  555. }
  556. {children}
  557. </View>
  558. );
  559. }
  560. export const ChargeStyle = StyleSheet.create({
  561. stationInfoView: {
  562. padding: 12,
  563. borderRadius: 10,
  564. marginBottom: 12,
  565. alignItems: 'center',
  566. flexDirection: 'row',
  567. ...ElevationObject(5),
  568. backgroundColor: colorLight,
  569. justifyContent: 'space-between'
  570. },
  571. infoGroup: {
  572. ...$padding(4, 2),
  573. alignItems: 'center'
  574. },
  575. infoTitle: {
  576. color: '#999',
  577. fontSize: 12,
  578. paddingTop: 1
  579. },
  580. infoText: {
  581. color: textPrimary,
  582. fontSize: 13,
  583. fontWeight: 'bold'
  584. },
  585. infoBoldNumber: {
  586. color: textPrimary,
  587. fontSize: 14,
  588. paddingTop: 3,
  589. fontWeight: 'bold'
  590. },
  591. infoStatus: {
  592. fontSize: 14,
  593. fontWeight: 'bold'
  594. },
  595. infoIcon: {
  596. width: 38,
  597. height: 38
  598. },
  599. itemDivide: {
  600. borderTopWidth: 1,
  601. borderTopColor: '#eee'
  602. },
  603. statusSelected: {
  604. color: textPrimary,
  605. ...$padding(4, 11),
  606. backgroundColor: colorAccent
  607. },
  608. statusAvailable: {
  609. color: '#90DB0A'
  610. },
  611. statusUnavailable: {
  612. color: '#666',
  613. fontSize: 12,
  614. paddingBottom: 1
  615. },
  616. statusAuthenticated: {
  617. color: '#90DB0A',
  618. fontSize: 12,
  619. paddingBottom: 1
  620. },
  621. statusError: {
  622. color: '#EF3340',
  623. fontSize: 12,
  624. paddingBottom: 1
  625. },
  626. statusWarning: {
  627. color: textLight,
  628. backgroundColor: colorAccent
  629. },
  630. rateText: {
  631. color: textPrimary,
  632. fontSize: 14,
  633. },
  634. ratePrice: {
  635. color: '#000',
  636. fontSize: 14,
  637. },
  638. authText: {
  639. color: '#000',
  640. fontSize: 12,
  641. paddingTop: 2
  642. }
  643. });