Charging.js 16 KB

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