Charging.js 17 KB

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