Charging.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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 { DialogMaxWidth } from './InfoDialog';
  11. import { QRResult } from './QRScan';
  12. export const circleSize = $vw(50.66) < 300 ? $vw(50.66) : 300;
  13. const batterySize = 0.463 * circleSize;
  14. const batteryWidth = 0.659*batterySize;
  15. export const TypeImage = {
  16. AC: require('../../images/charge/ic-type-ac.png'),
  17. DC: require('../../images/charge/ic-type-dc.png'),
  18. CHADEMO: require('../../images/charge/ic-type-chademo.png')
  19. }
  20. export const TypeImageList = [
  21. {
  22. name: 'AC',
  23. key: 'AC',
  24. icon: require('../../images/charge/ic-type-ac.png')
  25. }, {
  26. name: 'DC',
  27. key: 'DC',
  28. icon: require('../../images/charge/ic-type-dc.png')
  29. }, /*{
  30. name: 'Chademo',
  31. key: 'CHADEMO',
  32. icon: require('../../images/charge/ic-type-chademo.png')
  33. }*/
  34. ]
  35. export const getConnectTypeByKey = (key) => {
  36. for (let item of TypeImageList) {
  37. if (item.key == key) {
  38. return item;
  39. }
  40. }
  41. }
  42. export const CircleAnimate = ({isStart = false}) => {
  43. var rotate = useRef(new Animated.Value(0)).current;
  44. const spins = () => {
  45. Animated.timing(rotate, {
  46. toValue: 1,
  47. duration: 10000,
  48. easing: Easing.linear,
  49. useNativeDriver: true
  50. }).start(() => {
  51. rotate.setValue(0);
  52. spins();
  53. });
  54. }
  55. useEffect(() => {
  56. if (isStart) {
  57. spins();
  58. }
  59. }, [rotate]);
  60. const spin = rotate.interpolate({
  61. inputRange: [0, 1],
  62. outputRange: ['0deg', '360deg']
  63. })
  64. return (
  65. <Animated.View
  66. style={[
  67. styles.chargeCircle, {
  68. transform: [{
  69. rotate: spin
  70. }]
  71. }
  72. ]}>
  73. { isStart &&
  74. <View style={{ width: 25, height: 25, marginTop: -11}}>
  75. <RadialGradient
  76. x="50%" y="50%" rx="50%" ry="50%"
  77. colorList={[
  78. {offset: '0%', color: '#FFF', opacity: .8},
  79. {offset: '40%', color: '#FFF', opacity: .5},
  80. {offset: '70%', color: '#FFF', opacity: .3},
  81. {offset: '100%', color: '#FFF',opacity: 0}
  82. ]}/>
  83. </View>
  84. }
  85. </Animated.View>
  86. );
  87. }
  88. export const BatteryView = ({soc, isPending, isCharging}) => {
  89. var [powerPercent, setPercent] = useState(-1);
  90. /*var [powerText, setText] = useState(0);
  91. const autoCharge = () => {
  92. setTimeout(() => {
  93. const p = powerPercent + 0.001
  94. setPercent(Number(p.toFixed(4)))
  95. setText((powerPercent * 100).toFixed(0))
  96. if (powerPercent >= 1) {
  97. onComplete();
  98. }
  99. }, 50 + powerPercent * 100);
  100. }
  101. useEffect(() => {
  102. if (run && powerPercent <= 1) {
  103. autoCharge();
  104. }
  105. }, [powerPercent])*/
  106. useEffect(() => {
  107. if (isCharging) {
  108. try {
  109. var s = '-1' + soc;
  110. var d = '' + parseInt(s);
  111. d = d.replace('-1', '');
  112. if (d) {
  113. setPercent(parseInt(d))
  114. } else {
  115. setPercent(-1);
  116. }
  117. } catch (e) {
  118. setPercent(-1);
  119. }
  120. }
  121. }, [soc]);
  122. const getOpacity = (unit) => {
  123. var op = 1 * (powerPercent + unit);
  124. return op < 1 ? op : 1;
  125. }
  126. const getHeight = () => {
  127. if (powerPercent > 1) {
  128. return batterySize * powerPercent / 100;
  129. } else if (powerPercent > 0) {
  130. return batterySize * powerPercent;
  131. } else {
  132. return 0
  133. }
  134. }
  135. return (
  136. isCharging
  137. ? <View style={ui.center}>
  138. <ImageBackground
  139. style={{
  140. width: circleSize,
  141. height: circleSize,
  142. margin: 32,
  143. padding: 32,
  144. alignItems: 'center',
  145. justifyContent: 'center'
  146. }}
  147. source={require('../../images/charge/ic-charge-circle.png')}>
  148. <View style={styles.chargingView}>
  149. <View style={styles.plusLeftView}>
  150. <Image
  151. style={[styles.plusMiddle, { opacity: getOpacity(0.5)}]}
  152. source={require('../../images/charge/ic-plus-middle.png')}/>
  153. <Image
  154. style={[styles.plusSmall, { opacity: getOpacity(0.4)}]}
  155. source={require('../../images/charge/ic-plus-small.png')}/>
  156. </View>
  157. <View style={styles.batteryView}>
  158. <View style={[styles.batteryIcon]}>
  159. <Image
  160. style={styles.batteryIcon}
  161. source={require('../../images/charge/ic-battery-0.png')}/>
  162. <View style={[styles.batteryLayer, { height: getHeight() }]}>
  163. <Image
  164. style={[styles.batteryIcon]}
  165. source={require('../../images/charge/ic-battery-1.png')}/>
  166. </View>
  167. </View>
  168. { isPending
  169. ? <Text style={styles.batterySoc}>Initiating...</Text>
  170. : powerPercent != -1
  171. ? <Text style={styles.batteryPercent}>{powerPercent}%</Text>
  172. : <Text style={styles.batterySoc}>{'In Charging'}</Text>
  173. }
  174. </View>
  175. <View style={styles.plusRightView}>
  176. <Image
  177. style={[styles.plusLarge, { opacity: getOpacity(0.6)}]}
  178. source={require('../../images/charge/ic-plus-large.png')}/>
  179. </View>
  180. </View>
  181. <CircleAnimate isStart={true}/>
  182. </ImageBackground>
  183. </View>
  184. : <View style={styles.completeView}>
  185. <Image
  186. style={styles.disconnectIcon}
  187. source={require('../../images/charge/charge-complete.png')}/>
  188. <Text style={styles.completeTip}>Please disconnect and return connector to charging station</Text>
  189. </View>
  190. );
  191. }
  192. export const EnterStationDialog = ({visible, stationId, onConfirm, onClose}) => {
  193. var [inputStationId, setInput] = useState('')
  194. const enterStatioinId= () => {
  195. console.log(inputStationId);
  196. if (inputStationId) {
  197. QRResult.applyInputStation(inputStationId, stationId, (success, err) => {
  198. setInput('')
  199. if (success) {
  200. if (onConfirm) onConfirm()
  201. } else if (err) {
  202. toastShort(err)
  203. }
  204. if (onClose) onClose()
  205. });
  206. } else {
  207. toastShort('Please input Station ID')
  208. }
  209. }
  210. return (
  211. <Modal
  212. isVisible={visible}
  213. {...ModalProps}
  214. onBackdropPress={() => onClose}
  215. onBackButtonPress={() => onClose}>
  216. <View style={styles.stationDialog}>
  217. <Text style={styles.stationInputTitle}>Enter Station ID</Text>
  218. <TextInput
  219. style={styles.stationInput}
  220. defaultValue={inputStationId}
  221. placeholder='e.g: EVCTD0002-1'
  222. onChangeText={text => setInput(text)}
  223. />
  224. <View style={styles.dialogButtons}>
  225. <Button
  226. textSize={17}
  227. style={styles.buttonCancel}
  228. text='Close'
  229. onClick={onClose}/>
  230. <Button
  231. textSize={17}
  232. style={styles.buttonOK}
  233. text='Confirm'
  234. onClick={() => enterStatioinId()}/>
  235. </View>
  236. </View>
  237. </Modal>
  238. )
  239. }
  240. const styles = StyleSheet.create({
  241. chargingView: {
  242. width: circleSize,
  243. height: circleSize,
  244. flexDirection: 'row',
  245. alignItems: 'center'
  246. },
  247. chargeCircle: {
  248. top: 0,
  249. left: 0,
  250. zIndex: 10,
  251. width: circleSize,
  252. height: circleSize,
  253. position: 'absolute',
  254. alignItems: 'center',
  255. borderRadius: circleSize
  256. },
  257. batteryView: {
  258. paddingTop: 8,
  259. paddingLeft: 12,
  260. paddingRight: 12,
  261. alignItems: 'center',
  262. justifyContent: 'center'
  263. },
  264. batteryIcon: {
  265. width: batteryWidth,
  266. height: batterySize,
  267. position: 'relative',
  268. justifyContent: 'flex-end',
  269. },
  270. batteryLayer: {
  271. left: 0,
  272. right: 0,
  273. height: 0,
  274. width: batteryWidth,
  275. overflow: 'hidden',
  276. position: 'absolute',
  277. justifyContent: 'flex-end',
  278. },
  279. batteryPercent: {
  280. color: '#333',
  281. fontSize: 22,
  282. textAlign: 'center',
  283. paddingTop: 10,
  284. paddingBottom: 8
  285. },
  286. batterySoc: {
  287. color: '#333',
  288. fontSize: 18,
  289. textAlign: 'center',
  290. paddingTop: 10,
  291. paddingBottom: 10
  292. },
  293. plusLeftView: {
  294. flex: 1,
  295. height: batterySize + 10,
  296. marginBottom: 12,
  297. alignItems: 'flex-end',
  298. justifyContent: 'space-around'
  299. },
  300. plusRightView: {
  301. flex: 1,
  302. justifyContent: 'center'
  303. },
  304. plusLarge: {
  305. width: 24,
  306. height: 24
  307. },
  308. plusMiddle: {
  309. width: 18,
  310. height: 18
  311. },
  312. plusSmall: {
  313. width: 12,
  314. height: 12,
  315. marginBottom: 8
  316. },
  317. selectView: {
  318. position: 'relative'
  319. },
  320. selectIcon: {
  321. width: 18,
  322. height: 18
  323. },
  324. selectIconAbs: {
  325. top: -2,
  326. right: -4,
  327. zIndex: 2,
  328. position: 'absolute'
  329. },
  330. completeView: {
  331. height: circleSize,
  332. marginBottom: 16,
  333. alignItems: 'center',
  334. justifyContent: 'center'
  335. },
  336. disconnectIcon: {
  337. width: 100,
  338. height: 100
  339. },
  340. completeTip: {
  341. width: circleSize,
  342. color: '#333',
  343. fontSize: 16,
  344. paddingLeft: 16,
  345. paddingRight: 16,
  346. textAlign: 'center'
  347. },
  348. stationDialog: {
  349. padding: 16,
  350. marginLeft: 'auto',
  351. marginRight: 'auto',
  352. borderRadius: isIOS ? 20 : 3,
  353. width: DialogMaxWidth,
  354. backgroundColor: 'white'
  355. },
  356. stationInput: {
  357. ...$padding(4, 10),
  358. minHeight: 40,
  359. borderRadius: 3,
  360. backgroundColor: '#F0F0F0'
  361. },
  362. stationInputTitle: {
  363. fontSize: 15,
  364. textAlign: 'center',
  365. paddingBottom: 16
  366. },
  367. dialogButtons: {
  368. paddingTop: 16,
  369. paddingBottom: 8,
  370. flexDirection: 'row'
  371. },
  372. buttonOK: {
  373. flex: 1,
  374. marginLeft: 4,
  375. },
  376. buttonCancel: {
  377. flex: 1,
  378. backgroundColor: '#eee'
  379. }
  380. });
  381. export const SelectableIcon = ({selected, children}) => {
  382. return (
  383. <View style={styles.selectView}>
  384. {selected &&
  385. <Image
  386. style={[styles.selectIcon, children && styles.selectIconAbs]}
  387. source={require('../../images/charge/charge-item-select.png')}/>
  388. }
  389. {children}
  390. </View>
  391. );
  392. }
  393. export const ChargeStyle = StyleSheet.create({
  394. stationInfoView: {
  395. padding: 8,
  396. alignItems: 'center',
  397. flexDirection: 'row',
  398. justifyContent: 'space-between'
  399. },
  400. infoGroup: {
  401. ...$padding(4, 8),
  402. alignItems: 'center'
  403. },
  404. infoTitle: {
  405. color: '#999',
  406. fontSize: 12
  407. },
  408. infoText: {
  409. color: '#333',
  410. fontSize: 12,
  411. paddingTop: 4
  412. },
  413. infoBoldNumber: {
  414. color: '#333',
  415. fontSize: 14,
  416. paddingTop: 3,
  417. fontWeight: 'bold'
  418. },
  419. infoStatus: {
  420. fontSize: 12,
  421. borderRadius: 4,
  422. ...$padding(4, 10)
  423. },
  424. infoIcon: {
  425. width: 38,
  426. height: 38
  427. },
  428. itemDivide: {
  429. borderTopWidth: 1,
  430. borderTopColor: '#eee'
  431. },
  432. statusSelected: {
  433. color: '#333',
  434. ...$padding(4, 11),
  435. backgroundColor: colorAccent
  436. },
  437. statusAvailable: {
  438. color: 'white',
  439. backgroundColor: '#90DB0A'
  440. },
  441. statusUnavailable: {
  442. color: '#999',
  443. fontSize: 10,
  444. ...$padding(5, 8),
  445. backgroundColor: '#CCC'
  446. },
  447. statusWarning: {
  448. color: 'white',
  449. backgroundColor: colorAccent
  450. },
  451. rateText: {
  452. color: '#333',
  453. fontSize: 14,
  454. },
  455. ratePrice: {
  456. color: '#000',
  457. fontSize: 14,
  458. },
  459. authText: {
  460. color: '#000',
  461. fontSize: 12,
  462. paddingTop: 2
  463. }
  464. });