Charging.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  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}>{connectorInfo?.totalKWhDelivered ?? "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. const enterStatioinId= () => {
  271. //console.log(inputStationId);
  272. if (inputStationId) {
  273. QRResult.applyInputStation(inputStationId, stationId, (success, err) => {
  274. setInput('')
  275. if (success) {
  276. if (onConfirm) onConfirm()
  277. } else if (err) {
  278. toastShort(err)
  279. }
  280. if (onClose) onClose()
  281. });
  282. } else {
  283. toastShort('Please input Station ID')
  284. }
  285. }
  286. return (
  287. <Modal
  288. isVisible={visible}
  289. {...ModalProps}
  290. onBackdropPress={() => onClose}
  291. onBackButtonPress={() => onClose}>
  292. <View style={styles.stationDialog}>
  293. <Text style={styles.stationInputTitle}>Enter Station ID</Text>
  294. <TextInput
  295. style={styles.stationInput}
  296. defaultValue={inputStationId}
  297. placeholder='e.g: LEMOC0002-1'
  298. placeholderTextColor={textPlacehoder}
  299. onChangeText={text => setInput(text)}
  300. />
  301. <View style={styles.dialogButtons}>
  302. <Button
  303. textSize={17}
  304. style={styles.buttonCancel}
  305. viewStyle={ViewHeight(42)}
  306. text='Close'
  307. textColor={textCancel}
  308. onClick={onClose}/>
  309. <Button
  310. textSize={17}
  311. style={styles.buttonOK}
  312. viewStyle={ViewHeight(42)}
  313. text='Confirm'
  314. onClick={() => enterStatioinId()}/>
  315. </View>
  316. </View>
  317. </Modal>
  318. )
  319. }
  320. const styles = StyleSheet.create({
  321. chargingView: {
  322. width: circleSize,
  323. height: circleSize,
  324. flexDirection: 'row',
  325. alignItems: 'center'
  326. },
  327. chargeCircle: {
  328. top: 0,
  329. left: 0,
  330. zIndex: 10,
  331. width: circleSize,
  332. height: circleSize,
  333. position: 'absolute',
  334. alignItems: 'center',
  335. borderRadius: circleSize
  336. },
  337. batteryView: {
  338. paddingTop: 8,
  339. paddingLeft: 12,
  340. paddingRight: 12,
  341. alignItems: 'center',
  342. justifyContent: 'center'
  343. },
  344. batteryIcon: {
  345. width: batteryWidth,
  346. height: batterySize,
  347. position: 'relative',
  348. justifyContent: 'flex-end',
  349. },
  350. batteryLayer: {
  351. left: 0,
  352. right: 0,
  353. height: 0,
  354. width: batteryWidth,
  355. overflow: 'hidden',
  356. position: 'absolute',
  357. justifyContent: 'flex-end',
  358. },
  359. batteryPercent: {
  360. color: textPrimary,
  361. fontSize: 22,
  362. textAlign: 'center',
  363. paddingTop: 10,
  364. paddingBottom: 8
  365. },
  366. batterySoc: {
  367. color: textPrimary,
  368. fontSize: 18,
  369. textAlign: 'center',
  370. paddingTop: 10,
  371. paddingBottom: 10
  372. },
  373. plusLeftView: {
  374. flex: 1,
  375. height: batterySize + 10,
  376. marginBottom: 12,
  377. alignItems: 'flex-end',
  378. justifyContent: 'space-around'
  379. },
  380. plusRightView: {
  381. flex: 1,
  382. justifyContent: 'center'
  383. },
  384. plusLarge: {
  385. width: 24,
  386. height: 24
  387. },
  388. plusMiddle: {
  389. width: 18,
  390. height: 18
  391. },
  392. plusSmall: {
  393. width: 12,
  394. height: 12,
  395. marginBottom: 8
  396. },
  397. selectView: {
  398. position: 'relative'
  399. },
  400. selectIcon: {
  401. width: 18,
  402. height: 18
  403. },
  404. selectIconAbs: {
  405. top: -2,
  406. right: -4,
  407. zIndex: 2,
  408. position: 'absolute'
  409. },
  410. completeView: {
  411. height: circleSize,
  412. marginBottom: 16,
  413. alignItems: 'center',
  414. justifyContent: 'center'
  415. },
  416. disconnectIcon: {
  417. width: 100,
  418. height: 100
  419. },
  420. completeTip: {
  421. width: circleSize,
  422. color: textPrimary,
  423. fontSize: 16,
  424. paddingLeft: 16,
  425. paddingRight: 16,
  426. textAlign: 'center'
  427. },
  428. stationDialog: {
  429. padding: 16,
  430. marginLeft: 'auto',
  431. marginRight: 'auto',
  432. borderRadius: isIOS ? 20 : 3,
  433. width: DialogMaxWidth,
  434. backgroundColor: colorLight
  435. },
  436. stationInput: {
  437. ...$padding(4, 10),
  438. minHeight: 40,
  439. borderRadius: 3,
  440. backgroundColor: '#F0F0F0'
  441. },
  442. stationInputTitle: {
  443. fontSize: 15,
  444. textAlign: 'center',
  445. paddingBottom: 16
  446. },
  447. dialogButtons: {
  448. paddingTop: 24,
  449. paddingBottom: 8,
  450. flexDirection: 'row'
  451. },
  452. buttonOK: {
  453. flex: 1,
  454. marginLeft: 12,
  455. },
  456. buttonCancel: {
  457. flex: 1,
  458. borderWidth: 1,
  459. borderColor: colorCancel,
  460. backgroundColor: pageBackground
  461. },
  462. dashboardGreyView: {
  463. padding: 16,
  464. marginTop: 16,
  465. marginBottom: 16,
  466. borderRadius: 6,
  467. ...ElevationObject(5),
  468. backgroundColor: pageBackground
  469. },
  470. dashboardStartView: {
  471. padding: 16,
  472. marginTop: 16,
  473. marginBottom: 16,
  474. borderRadius: 6,
  475. ...ElevationObject(5)
  476. },
  477. dashboardGradientView: {
  478. top: 0,
  479. left: 0,
  480. right: 0,
  481. bottom: 0,
  482. borderRadius: 6,
  483. overflow: 'hidden',
  484. position: "absolute",
  485. },
  486. dashboardTitle: {
  487. color: textPrimary,
  488. fontSize: 25,
  489. fontWeight: 'bold',
  490. textAlign: 'center',
  491. paddingBottom: 16
  492. },
  493. dashboardTitleWhite: {
  494. color: textLight,
  495. fontSize: 25,
  496. fontWeight: 'bold',
  497. textAlign: 'center',
  498. paddingBottom: 16
  499. },
  500. dashboardItemGroup: {
  501. marginRight: -12,
  502. marginBottom: -32,
  503. alignItems: 'center',
  504. flexDirection: 'row'
  505. },
  506. dashboardItemView: {
  507. flex: 1,
  508. marginRight: 12,
  509. borderRadius: 6,
  510. ...$padding(10, 6, 14),
  511. ...ElevationObject(5),
  512. backgroundColor: colorLight
  513. },
  514. dashboardItemTitle: {
  515. color: textCancel,
  516. fontSize: 12,
  517. textAlign: 'center',
  518. paddingTop: 2
  519. },
  520. dashboardItemValue: {
  521. color: textPrimary,
  522. fontSize: 14,
  523. textAlign: 'center'
  524. },
  525. dashboardItemValueWeight: {
  526. color: textPrimary,
  527. fontSize: 14,
  528. fontWeight: 'bold',
  529. textAlign: 'center'
  530. }
  531. });
  532. export const SelectableIcon = ({selected, children}) => {
  533. return (
  534. <View style={styles.selectView}>
  535. {selected &&
  536. <View style={[styles.selectIcon, children && styles.selectIconAbs]}>
  537. <ChargeItemSelect size={18} selected={true}/>
  538. </View>
  539. }
  540. {children}
  541. </View>
  542. );
  543. }
  544. export const ChargeStyle = StyleSheet.create({
  545. stationInfoView: {
  546. padding: 12,
  547. borderRadius: 10,
  548. marginBottom: 12,
  549. alignItems: 'center',
  550. flexDirection: 'row',
  551. ...ElevationObject(5),
  552. backgroundColor: colorLight,
  553. justifyContent: 'space-between'
  554. },
  555. infoGroup: {
  556. ...$padding(4, 2),
  557. alignItems: 'center'
  558. },
  559. infoTitle: {
  560. color: '#999',
  561. fontSize: 12,
  562. paddingTop: 1
  563. },
  564. infoText: {
  565. color: textPrimary,
  566. fontSize: 13,
  567. fontWeight: 'bold'
  568. },
  569. infoBoldNumber: {
  570. color: textPrimary,
  571. fontSize: 14,
  572. paddingTop: 3,
  573. fontWeight: 'bold'
  574. },
  575. infoStatus: {
  576. fontSize: 14,
  577. fontWeight: 'bold'
  578. },
  579. infoIcon: {
  580. width: 38,
  581. height: 38
  582. },
  583. itemDivide: {
  584. borderTopWidth: 1,
  585. borderTopColor: '#eee'
  586. },
  587. statusSelected: {
  588. color: textPrimary,
  589. ...$padding(4, 11),
  590. backgroundColor: colorAccent
  591. },
  592. statusAvailable: {
  593. color: '#90DB0A'
  594. },
  595. statusUnavailable: {
  596. color: '#666',
  597. fontSize: 12,
  598. paddingBottom: 1
  599. },
  600. statusAuthenticated: {
  601. color: '#90DB0A',
  602. fontSize: 12,
  603. paddingBottom: 1
  604. },
  605. statusError: {
  606. color: '#EF3340',
  607. fontSize: 12,
  608. paddingBottom: 1
  609. },
  610. statusWarning: {
  611. color: textLight,
  612. backgroundColor: colorAccent
  613. },
  614. rateText: {
  615. color: textPrimary,
  616. fontSize: 14,
  617. },
  618. ratePrice: {
  619. color: '#000',
  620. fontSize: 14,
  621. },
  622. authText: {
  623. color: '#000',
  624. fontSize: 12,
  625. paddingTop: 2
  626. }
  627. });