浏览代码

change app themes and UI

vbea 3 年之前
父节点
当前提交
5b43fbad18
共有 89 个文件被更改,包括 10743 次插入2199 次删除
  1. 2 2
      Strides-APP/android/app/version.properties
  2. 1 1
      Strides-APP/app.json
  3. 17 1
      Strides-APP/app/api/apiUser.js
  4. 3 2
      Strides-APP/app/components/Appbar.js
  5. 46 0
      Strides-APP/app/components/BadgeSelectItem.js
  6. 1 1
      Strides-APP/app/components/BottomModal.js
  7. 8 10
      Strides-APP/app/components/Button.js
  8. 1 1
      Strides-APP/app/components/CheckBox.js
  9. 9 6
      Strides-APP/app/components/Dropdown.js
  10. 0 6
      Strides-APP/app/components/MyRefreshControl.js
  11. 13 0
      Strides-APP/app/components/ThemesConfig.js
  12. 11 6
      Strides-APP/app/components/Toolbar.js
  13. 12 0
      Strides-APP/app/icons/VehicleType.js
  14. 二进制
      Strides-APP/app/images/icon/search-loading.gif
  15. 二进制
      Strides-APP/app/images/site/error-A1.jpg
  16. 二进制
      Strides-APP/app/images/site/error-A4.jpg
  17. 二进制
      Strides-APP/app/images/site/error-A5.jpg
  18. 二进制
      Strides-APP/app/images/site/error-A9.jpg
  19. 二进制
      Strides-APP/app/images/top-feedback.png
  20. 二进制
      Strides-APP/app/images/user/bg-vehicles.png
  21. 二进制
      Strides-APP/app/images/user/card-account.png
  22. 二进制
      Strides-APP/app/images/user/card-notification.png
  23. 二进制
      Strides-APP/app/images/user/card-vehicle.png
  24. 二进制
      Strides-APP/app/images/user/card-wallet.png
  25. 二进制
      Strides-APP/app/images/user/ic-vehicle-model.png
  26. 二进制
      Strides-APP/app/images/user/sign-email.png
  27. 二进制
      Strides-APP/app/images/user/sign-otp.png
  28. 二进制
      Strides-APP/app/images/user/sign-password.png
  29. 二进制
      Strides-APP/app/images/wallet/ic-type-charge.png
  30. 二进制
      Strides-APP/app/images/wallet/ic-type-payment.png
  31. 二进制
      Strides-APP/app/images/wallet/payment-grab-active.png
  32. 二进制
      Strides-APP/app/images/wallet/payment-grab.png
  33. 二进制
      Strides-APP/app/images/wallet/tab-left.png
  34. 二进制
      Strides-APP/app/images/wallet/tab-right.png
  35. 109 37
      Strides-APP/app/pages/Router.js
  36. 77 16
      Strides-APP/app/pages/Settings.js
  37. 1 1
      Strides-APP/app/pages/charge/Details.js
  38. 1 1
      Strides-APP/app/pages/charge/Summary.js
  39. 277 0
      Strides-APP/app/pages/chargeV2/ChargeAdapter.js
  40. 649 0
      Strides-APP/app/pages/chargeV2/Charging.js
  41. 329 0
      Strides-APP/app/pages/chargeV2/InfoDialog.js
  42. 44 0
      Strides-APP/app/pages/chargeV2/PagerUtil.js
  43. 64 0
      Strides-APP/app/pages/chargeV2/Payment.js
  44. 180 0
      Strides-APP/app/pages/chargeV2/QRScan.js
  45. 304 0
      Strides-APP/app/pages/chargeV2/Summary.js
  46. 812 0
      Strides-APP/app/pages/chargeV2/TabCharge.js
  47. 22 0
      Strides-APP/app/pages/chargeV2/TabExplore.js
  48. 94 0
      Strides-APP/app/pages/chargeV2/TabInfos.js
  49. 530 0
      Strides-APP/app/pages/chargeV2/TabReserve.js
  50. 205 123
      Strides-APP/app/pages/home/Drawer.js
  51. 7 6
      Strides-APP/app/pages/home/maps/BottomSiteInfo.js
  52. 25 20
      Strides-APP/app/pages/home/maps/Filter.js
  53. 3 3
      Strides-APP/app/pages/home/maps/TopInfo.js
  54. 15 18
      Strides-APP/app/pages/my/AddVehicle.js
  55. 15 16
      Strides-APP/app/pages/my/EditProfile.js
  56. 10 12
      Strides-APP/app/pages/my/EditVehicle.js
  57. 98 73
      Strides-APP/app/pages/my/Feedback.js
  58. 457 0
      Strides-APP/app/pages/my/ProfileV2.js
  59. 97 17
      Strides-APP/app/pages/my/VehicleList.js
  60. 46 0
      Strides-APP/app/pages/search/ConnectType.js
  61. 129 0
      Strides-APP/app/pages/search/ListView.js
  62. 142 0
      Strides-APP/app/pages/search/ListViewV2.js
  63. 238 0
      Strides-APP/app/pages/search/Search.js
  64. 242 0
      Strides-APP/app/pages/search/SearchV2.js
  65. 59 19
      Strides-APP/app/pages/sign/Login.js
  66. 305 301
      Strides-APP/app/pages/sign/LoginV2.js
  67. 35 77
      Strides-APP/app/pages/sign/Register.js
  68. 674 0
      Strides-APP/app/pages/sign/RegisterDriver.js
  69. 693 0
      Strides-APP/app/pages/sign/RegisterPublic.js
  70. 719 767
      Strides-APP/app/pages/sign/RegisterV2.js
  71. 702 0
      Strides-APP/app/pages/sign/RegisterV3.js
  72. 779 0
      Strides-APP/app/pages/sign/RegisterV4.js
  73. 12 11
      Strides-APP/app/pages/sign/ResetPassword.js
  74. 398 358
      Strides-APP/app/pages/sign/ResetPasswordV2.js
  75. 139 0
      Strides-APP/app/pages/sign/StrengthView.js
  76. 13 11
      Strides-APP/app/pages/wallet/History.js
  77. 178 72
      Strides-APP/app/pages/wallet/Overview.js
  78. 44 22
      Strides-APP/app/pages/wallet/Payment.js
  79. 2 3
      Strides-APP/app/pages/wallet/PaythodList.js
  80. 10 8
      Strides-APP/app/pages/wallet/Topup.js
  81. 260 0
      Strides-APP/app/pages/wallet/TopupNew.js
  82. 176 0
      Strides-APP/app/pages/wallet/TopupPaythod.js
  83. 3 86
      Strides-APP/app/pages/wallet/TopupV2.js
  84. 94 60
      Strides-APP/app/pages/wallet/Wallet.js
  85. 10 21
      Strides-APP/app/utils/constant.js
  86. 61 0
      Strides-APP/app/utils/themes.js
  87. 25 0
      Strides-APP/app/utils/utils.js
  88. 4 4
      Strides-APP/index.js
  89. 2 0
      Strides-APP/package.json

+ 2 - 2
Strides-APP/android/app/version.properties

@@ -1,2 +1,2 @@
-#Sun Jan 29 14:54:53 CST 2023
-VERSION_CODE=88
+#Thu Feb 09 11:59:27 CST 2023
+VERSION_CODE=91

+ 1 - 1
Strides-APP/app.json

@@ -3,6 +3,6 @@
   "displayName": "ChargEco",
   "versionCode": 85,
   "versionName": "V1.0.4",
-  "product": true,
+  "product": false,
   "debug": false
 }

+ 17 - 1
Strides-APP/app/api/apiUser.js

@@ -9,6 +9,9 @@ export default user = {
   register: (param) => {
     return post(prefix + 'register', param);
   },
+  registerDriver: (param) => {
+    return post(prefix + 'upgradeDriver', param);
+  },
   getConmpany: () => {
     return get(prefix + 'getFleetCompanyList');
   },
@@ -54,8 +57,21 @@ export default user = {
   getConnectorType: () => {
     return get(prefix + 'getConnectorType', {});
   },
+  /**
+   * 重置密码时发送邮箱验证码接口
+   * @param {String} email 邮箱地址
+   * @returns Promise(Response)
+   */
   sendVerificationCode: (email) => {
-    return get(prefix + 'sendVerificationCode', {email: email});
+    return get(prefix + 'sendVerificationCode', {email: email, type: "reset"});
+  },
+  /**
+   * 注册和重置密码时发送邮箱验证码接口
+   * @param {Object} params {email, type}
+   * @returns Promise(Response)
+   */
+  sendVerificationCodeV2: (params) => {
+    return get(prefix + 'sendVerificationCode', params);
   },
   updatePassword: (params) => {
     return post(prefix + 'updatePassword', params);

+ 3 - 2
Strides-APP/app/components/Appbar.js

@@ -19,7 +19,7 @@ export const BackButton = ({navigation}) => {
 }
 
 export const BackIcon = () => {
-  return <MaterialIcons name={isIOS ? 'arrow-back-ios' : 'arrow-back'} size={24} color={'#333333'} />
+  return <MaterialIcons name={isIOS ? 'arrow-back-ios' : 'arrow-back'} size={24} color={colorDark} />
 }
 
 export const StationBack = ({bottom}) => {
@@ -58,7 +58,7 @@ export const Styles = StyleSheet.create({
     alignItems: 'center'
   },
   logo: {
-    width:123.8,
+    width: 123.8,
     height: 38.95
   }
 });
@@ -71,6 +71,7 @@ export default Appbar = (props) => {
         <Image
           source={require('../images/tool-logo.png')}
           style={Styles.logo}
+          resizeMode="contain"
         />
       </View>
     </View>

+ 46 - 0
Strides-APP/app/components/BadgeSelectItem.js

@@ -0,0 +1,46 @@
+import React from 'react';
+import { Pressable, Text, View } from 'react-native';
+import Svg, { Path } from 'react-native-svg';
+
+const BadgeSelectItem = ({
+  children,
+  style={},
+  checked=false,
+  onPress,
+  iconSize=28,
+  tintColor=colorAccent,
+  borderColor="transparent"
+}) => (
+  <Pressable onPress={onPress} style={mergeStyle(style, checked ? tintColor : borderColor)}>
+    {children}
+    { checked &&
+      <Svg 
+        width={iconSize}
+        height={iconSize}
+        viewBox="0 0 40 40"
+        style={{top: -1, right: -1, position: 'absolute'}}>
+        <Path
+          fill={tintColor}
+          d="M28 0H0L40 40V12C40 5.37258 34.6274 -30 28 0Z" />
+        <Path
+          fill="white"
+          d="M34.8965 10.5142L27.1892 18.6408C27.0371 18.801 26.7733 18.7828 26.5999 18.6L26.0834 18.0554L22.63 14.4141C22.4567 14.2313 22.4567 13.935 22.63 13.7522L23.5718 12.7592C23.7451 12.5762 24.0262 12.5762 24.1996 12.7592L26.9138 15.6211L33.3268 8.8593C33.4789 8.69893 33.7426 8.71717 33.916 8.89998L34.8577 9.89294C35.0311 10.0759 35.0484 10.354 34.8965 10.5142Z"/>
+      </Svg>
+    }
+  </Pressable>
+);
+
+const mergeStyle = (style, color) => {
+  const def = {borderColor: color, borderWidth: 1, borderRadius: 10, overflow: 'hidden'}
+  if (Array.isArray(style)) {
+    let res = {}
+    for (let s of style) {
+      res = {...res, ...s};
+    }
+    style = res;
+  }
+  var s = Object.assign(def, style);
+  return s;
+}
+
+export default BadgeSelectItem;

+ 1 - 1
Strides-APP/app/components/BottomModal.js

@@ -33,7 +33,7 @@ const styles = StyleSheet.create({
   },
   bottomModalContent: {
     maxHeight: $vht(isIOS ? 92 : 96),
-    backgroundColor: 'white',
+    backgroundColor: pageBackground,
     ...$borderRadius(20, 20, 0, 0)
   },
 })

+ 8 - 10
Strides-APP/app/components/Button.js

@@ -1,10 +1,6 @@
 import React, { useEffect } from 'react';
 import { Pressable, StyleSheet, Text, View } from 'react-native';
 
-//主题配置
-const tintColor = colorPrimary;
-const textDefault = '#fff';
-
 export default Button = ({
   style = styles.buttonView,
   text,
@@ -17,10 +13,10 @@ export default Button = ({
   onLongClick,
   disabled = false,
   elevation = 0,
-  borderRadius = 4,
+  borderRadius = 40,
 }) => {
   var start = {}, end = {};
-  const isSamsung = BRAND == 'samsung';
+  //const isSamsung = BRAND == 'samsung';
   return (
     <View style={getElevation(style, disabled, elevation, borderRadius)}>
       <Pressable
@@ -135,7 +131,7 @@ const getElevation = (style, d, e, r) => {
     s.borderRadius = r;
   }
   if (!s.backgroundColor) {
-    s.backgroundColor = tintColor
+    s.backgroundColor = styles.buttonView.backgroundColor
   }
   if (!d) {
     var ele = e ? e : style.elevation ? style.elevation : 0
@@ -184,7 +180,9 @@ export const ViewHeightPadding = (height, padding=0) => {
 
 const styles = StyleSheet.create({
   buttonView: {
-    backgroundColor: colorPrimary
+    //调整默认按钮颜色
+    backgroundColor: colorAccent,
+    //backgroundColor: colorPrimary
   },
   button: {
     flex: 1,
@@ -202,10 +200,10 @@ const styles = StyleSheet.create({
     paddingRight: 16,
     alignItems: 'center',
     justifyContent: 'center',
-    backgroundColor: tintColor
+    backgroundColor: colorAccent//colorPrimary
   },
   buttonText: {
-    color: textDefault,
+    color: textButton,
     fontSize: 16,
     fontWeight: 'bold',
     textAlign: 'center',

+ 1 - 1
Strides-APP/app/components/CheckBox.js

@@ -6,7 +6,7 @@ const CheckBox = ({
   value=false,
   disabled=false,
   onValueChange,
-  tintColor=colorPrimary,
+  tintColor=colorAccent,
   iosSize=24
 }) => {
   if (isIOS) {

+ 9 - 6
Strides-APP/app/components/Dropdown.js

@@ -24,6 +24,7 @@ export default Dropdown = ({
     textStyle = styles.valueText,
     placeholderStyle=styles.placeText,
     placeholder='',
+    showText = true,
     showIcon = true,
     iconColor = '#888',
     iconStyle = styles.iconStyle,
@@ -114,9 +115,11 @@ export default Dropdown = ({
         style={style}
         android_ripple={rippleStyle}
         onPress={() => showList()}>
-        { selected 
-          ? <Text style={[ui.flex1, textStyle]} numberOfLines={1}>{selected}</Text>
-          : <Text style={[textStyle, placeholderStyle]} numberOfLines={1}>{placeholder}</Text>
+        { showText && 
+          ( selected 
+            ? <Text style={[ui.flex1, textStyle]} numberOfLines={1}>{selected}</Text>
+            : <Text style={[textStyle, placeholderStyle]} numberOfLines={1}>{placeholder}</Text>
+          )
         }
         { showIcon && 
           <Entypo 
@@ -160,7 +163,7 @@ const styles = StyleSheet.create({
     marginRight: 'auto',
     paddingTop: isIOS ? 12 : 8,
     paddingBottom: isIOS ? 12 : 8,
-    backgroundColor: 'white',
+    backgroundColor: colorLight,
     borderRadius: isIOS ? 10 : 3
   },
   title: {
@@ -183,11 +186,11 @@ const styles = StyleSheet.create({
   },
   itemView: {
     borderRadius: 0,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   itemText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     textAlign: 'left',
     fontWeight: 'normal'

+ 0 - 6
Strides-APP/app/components/MyRefreshControl.js

@@ -1,6 +0,0 @@
-export const MyRefreshProps = {
-  title: 'Refresh',
-  titleColor: '#999',
-  colors: [colorAccent, colorPrimaryDark],
-  tintColor: colorAccent
-}

+ 13 - 0
Strides-APP/app/components/ThemesConfig.js

@@ -0,0 +1,13 @@
+export const MyRefreshProps = {
+  title: 'Pulldown to Refresh',
+  titleColor: textCancel,
+  colors: [colorAccent, colorPrimary],
+  tintColor: colorAccent
+}
+
+export const UploadThemes = {
+  cropperStatusBarColor: colorLight,
+  cropperToolbarColor: colorLight,
+  cropperToolbarWidgetColor: textDark,
+  cropperActiveWidgetColor: colorAccent
+}

+ 11 - 6
Strides-APP/app/components/Toolbar.js

@@ -6,11 +6,7 @@ export const BackButton = () => {
   return (
     <Pressable
       style={Styles.backIcon}
-      android_ripple = {{
-        color: '#909090',
-        radius: 22,
-        borderless: true
-      }}
+      android_ripple = {rippleLessIcon}
       onPress={() => {
         goBack();
       }}>
@@ -20,7 +16,7 @@ export const BackButton = () => {
 }
 
 export const BackIcon = () => {
-  return <MaterialIcons name={isIOS ? 'arrow-back-ios' : 'arrow-back'} size={24} color={'#333333'} />
+  return <MaterialIcons name={isIOS ? 'arrow-back-ios' : 'arrow-back'} size={24} color={colorDark} />
 }
 
 export const StationBack = ({bottom = 24, scale=1.0}) => {
@@ -74,6 +70,14 @@ export const Styles = StyleSheet.create({
   logo: {
     width:123.8,
     height: 38.95
+  },
+  rightIcon: {
+    width: 22,
+    height: 22,
+    opacity: 0.9
+  },
+  iconOpacity: {
+    opacity: 0.9
   }
 });
 
@@ -85,6 +89,7 @@ export default Toolbar = (props) => {
         <Image
           source={require('../images/tool-logo.png')}
           style={Styles.logo}
+          resizeMode="contain"
         />
       </View>
     </View>

+ 12 - 0
Strides-APP/app/icons/VehicleType.js

@@ -0,0 +1,12 @@
+import React from 'react';
+import Svg, { Path } from 'react-native-svg';
+
+const VehicleType = ({size=8}) => (
+  <Svg width={size} height={size} viewBox={"0 0 8 8"}>
+    <Path
+      fill={colorLight}
+      d="M7.34219 3.44688V0.662505C7.34219 0.373443 6.97969 0.0804741 6.60391 0.0984428H1.39531C0.967188 0.0984428 0.640625 0.356255 0.657812 0.662505L0.74375 3.46329H0.09375L0.100781 5.67501L1.77188 6.78907V7.90313H6.22813V6.78907L7.89922 5.67501L7.90625 3.46251H7.25469L7.34219 3.44688ZM6.22813 3.44688H5.67109V1.77579H4.55703V3.44688H3.44297V1.77579H2.32891V3.44688H1.77188V1.21876L6.27812 1.07422V3.13672L6.22813 3.44688Z"/>
+  </Svg>
+);
+
+export default VehicleType;

二进制
Strides-APP/app/images/icon/search-loading.gif


二进制
Strides-APP/app/images/site/error-A1.jpg


二进制
Strides-APP/app/images/site/error-A4.jpg


二进制
Strides-APP/app/images/site/error-A5.jpg


二进制
Strides-APP/app/images/site/error-A9.jpg


二进制
Strides-APP/app/images/top-feedback.png


二进制
Strides-APP/app/images/user/bg-vehicles.png


二进制
Strides-APP/app/images/user/card-account.png


二进制
Strides-APP/app/images/user/card-notification.png


二进制
Strides-APP/app/images/user/card-vehicle.png


二进制
Strides-APP/app/images/user/card-wallet.png


二进制
Strides-APP/app/images/user/ic-vehicle-model.png


二进制
Strides-APP/app/images/user/sign-email.png


二进制
Strides-APP/app/images/user/sign-otp.png


二进制
Strides-APP/app/images/user/sign-password.png


二进制
Strides-APP/app/images/wallet/ic-type-charge.png


二进制
Strides-APP/app/images/wallet/ic-type-payment.png


二进制
Strides-APP/app/images/wallet/payment-grab-active.png


二进制
Strides-APP/app/images/wallet/payment-grab.png


二进制
Strides-APP/app/images/wallet/tab-left.png


二进制
Strides-APP/app/images/wallet/tab-right.png


+ 109 - 37
Strides-APP/app/pages/Router.js

@@ -3,34 +3,43 @@
  * @邠心vbe on 2021/03/22
  */
 import React, { useEffect, useRef } from 'react';
+import { Pressable } from 'react-native';
 import { NavigationContainer } from '@react-navigation/native';
 import { createStackNavigator, TransitionPresets } from '@react-navigation/stack';
 import { enableScreens } from 'react-native-screens';
+import { Styles } from '../components/Toolbar';
+import app from '../../app.json';
 import About from './About';
 import Launcher from './Launch';
-import Login from './sign/LoginV2';
-import Regist from './sign/RegisterV2'
+import Login from './sign/Login';
+import Regist from './sign/RegisterV2';
+import RegisterV3 from './sign/RegisterV3';
+import RegisterV4 from './sign/RegisterV4';
+import RegisterPublic from './sign/RegisterPublic';
+import RegisterDriver from './sign/RegisterDriver';
 import Home from './home/Drawer';
-import Search from './home/Search';
+import Search from './search/SearchV2';
 import ChargeDetails from './charge/Details';
-import QRScan from './charge/QRScan';
+import QRScan from './chargeV2/QRScan';
 import Feedback from './my/Feedback';
 import Privacy from './my/Privacy';
-import Profile from './my/Profile';
+import Profile from './my/ProfileV2';
 import Condition from './my/Condition';
-import Summary from './charge/Summary';
+import Summary from './chargeV2/Summary';
 import Rating from './charge/Rating';
 import Wallet from './wallet/Wallet';
 import EditProfile from './my/EditProfile';
 import Referral from './my/Referral';
 import Topup from './wallet/Topup'; //not 2C2P
 import TopupV2 from './wallet/TopupV2'; //2C2P payment
+import TopupNew from './wallet/TopupNew';
 import AddCard from './wallet/AddCard';
 import FormCard from './payment/FormCard';
+import VehicleList from './my/VehicleList';
 import AddVehicle from './my/AddVehicle';
+import EditVehicle from './my/EditVehicle';
 import PayNow from './payment/PayNow';
 import CreditCard from './payment/CreditCard';
-import EditVehicle from './my/EditVehicle';
 import EditAddress from './my/EditAddress';
 import Notify from './home/Notify';
 import Test from './home/maps/Test';
@@ -39,7 +48,7 @@ import PaymentMethod from './payment/PaymentMethod';
 import PayPerUse from './payment/PayPerUse';
 import PaymentWeb from './payment/PaymentWeb';
 import Settings from './Settings';
-import app from '../../app.json'
+import ChargeAdapter from './chargeV2/ChargeAdapter';
 
 export var PageList = {
   'splash': {
@@ -57,12 +66,25 @@ export var PageList = {
     component: Login
   },
   'register': {
-    component: Regist
+    component: RegisterV4,
+    title: 'Public Registration'
+  },
+  'registerPublic': {
+    component: RegisterPublic,
+    title: 'Public Registration'
+  },
+  'registerFleet': {
+    component: RegisterDriver,
+    title: 'Fleet / PHV Registration'
   },
   'chargeDetail': {
     title: 'Charging Site',
     component: ChargeDetails
   },
+  'chargeDetailPage': {
+    title: 'Charging Site',
+    component: ChargeAdapter
+  },
   'scanqr': {
     title: 'QR Scan',
     component: QRScan
@@ -107,10 +129,6 @@ export var PageList = {
     title: 'Edit Address',
     component: EditAddress
   },
-  'editVehicle': {
-    title: 'Update Vehicle',
-    component: EditVehicle
-  },
   'referral': {
     title: 'Referral',
     component: Referral
@@ -123,14 +141,41 @@ export var PageList = {
     title: 'Top Up',
     component: TopupV2
   },
+  'topupNew': {
+    title: 'Top Up',
+    component: TopupNew
+  },
   'addCard': {
     title: 'Add Cards',
     component: AddCard
   },
+  'myVehicles': {
+    title: 'My Vehicles',
+    component: VehicleList,
+    options: {
+      headerRight: () => (
+        <Pressable
+          style={Styles.backIcon}
+          android_ripple={rippleLessIcon}
+          onPress={() => startPage(PageList.addVehicle)}>
+          <MaterialCommunityIcons
+            name='plus'
+            color='#fff'
+            size={24}
+            style={Styles.iconOpacity}
+          />
+        </Pressable>
+      )
+    }
+  },
   'addVehicle': {
     title: 'Add Vehicle',
     component: AddVehicle
   },
+  'editVehicle': {
+    title: 'Update Vehicle',
+    component: EditVehicle
+  },
   'paynow': {
     title: 'PAYNOW',
     component: PayNow
@@ -163,6 +208,11 @@ export var PageList = {
     component: Test
   },
   'forgotPassword': {
+    title: 'Forgot Password',
+    component: ResetPassword
+  },
+  'changePassword': {
+    title: 'Account Security',
     component: ResetPassword
   },
   'settings': {
@@ -174,6 +224,47 @@ export var PageList = {
 const Stack = createStackNavigator();
 enableScreens();
 
+/**
+ * 配置APP主题色
+ */
+const themeColor = {
+  text: textPrimary,
+  card: colorThemes,
+  primary: colorPrimary,
+  background: pageBackground,
+  notification: colorDark
+}
+
+const noTitle = (opt = {}) => {
+  return {
+    title: isIOS ? 'Back' : app.displayName,
+    headerShown: false,
+    ...opt
+  }
+}
+
+/**
+ * 配置标题
+ * @param {String} title 页面标题
+ * @param {Object} opt 标题栏选项
+ * @returns 
+ */
+const Title = (title, opt = {}) => {
+  return {
+    title: title,
+    headerShown: true,
+    headerStyle: {
+      ...titleHeight(),
+      elevation: 0,
+      backgroundColor: colorLight //配置标题栏背景
+    },
+    headerTintColor: pageTitleTint, //配置标题栏文字和图标颜色
+    headerBackTitle: ' ', //配置iOS返回按钮文字
+    ...opt
+  }
+}
+
+
 var bakPages = undefined;
 
 function getPages() {
@@ -221,7 +312,11 @@ const Router = () => {
   }, []);
   return (
     <NavigationContainer
-      ref={navigation}>
+      ref={navigation}
+      theme={{
+        dark: darkMode,
+        colors: themeColor
+      }}>
       <Stack.Navigator
         initialRouteName='splash'
         screenOptions={{animationEnabled: true, ...TransitionPresets.SlideFromRightIOS }}>
@@ -231,29 +326,6 @@ const Router = () => {
   )
 }
 
-const noTitle = (opt = {}) => {
-  return {
-    title: isIOS ? 'Back' : app.displayName,
-    headerShown: false,
-    ...opt
-  }
-}
-
-const Title = (title, opt = {}) => {
-  return {
-    title: title,
-    headerShown: true,
-    headerStyle: {
-      ...titleHeight(),
-      elevation: 0,
-      backgroundColor: colorThemes
-    },
-    headerTintColor: '#232323',
-    headerBackTitle: ' ',
-    ...opt
-  }
-}
-
 const titleHeight = () => {
   return isIOS ? {} : {height: toolbarSize};
 }

+ 77 - 16
Strides-APP/app/pages/Settings.js

@@ -3,8 +3,8 @@
  * @邠心vbe on 2022/12/9
  */
 import React, { Component } from 'react';
-import { View, Text, StyleSheet, Switch } from 'react-native';
-import { Value } from 'react-native-reanimated';
+import { View, Text, StyleSheet, Switch, Pressable } from 'react-native';
+import apiUser from '../api/apiUser';
 import Button from '../components/Button';
 import Dropdown from '../components/Dropdown';
 import storage from '../utils/storage';
@@ -39,15 +39,30 @@ export default class Settings extends Component {
         alwaysLocation: true,
         refreshInterval: 10
       },
-      intervalList: [10, 15, 20, 30, 60, 90, 120]
+      userInfo: {
+        notifyLowBalance: false,
+        notifyChargingComplete: false,
+        notifyPromotionsOffers: false
+      },
+      intervalList: [10, 15, 20, 30, 60, 90, 120],
+      switchStyle: isIOS ? { false: "#B2B2B2", true: colorAccent } : null,
     };
+    this.changed = false;
+    //global.colorAccent = "#00A9FF"
+    //global.colorPrimary = "#00A9FF"
   }
 
   componentDidMount() {
     this.props.navigation.addListener('blur', () => {
       //console.log("保存设置");
-      global.settingsInfo = this.state.settings
-      storage.setStorageJson(SETTINGS_KEY, this.state.settings)
+      this.saveSettings();
+    });
+    this.props.navigation.addListener('focus', () => {
+      getUserInfo(info => {
+        this.setState({
+          userInfo: info
+        });
+      }, true);
     });
     SettingUtil.getSettings(res => {
       const info = this.state.settings;
@@ -70,6 +85,14 @@ export default class Settings extends Component {
     })
   }
 
+  changeNotifySwitch(key, value) {
+    userInfo[key] = value;
+    this.setState({
+      userInfo: userInfo
+    });
+    this.changed = true;
+  }
+
   changeSettings(key, value) {
     this.state.settings[key] = value
     this.setState({
@@ -77,9 +100,48 @@ export default class Settings extends Component {
     })
   }
 
+  saveSettings() {
+    global.settingsInfo = this.state.settings
+    storage.setStorageJson(SETTINGS_KEY, this.state.settings)
+    if (this.changed) {
+      apiUser.setNotifySwitch(this.state.userInfo);
+    }
+  }
+
   render() {
     return (
       <View style={ui.flex1}>
+        <Text style={styles.title}>Notification</Text>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => this.changeNotifySwitch("notifyChargingComplete", !userInfo.notifyChargingComplete)}>
+          <Text style={styles.buttonText}>Notify me when charging complete</Text>
+          <Switch
+            value={this.state.userInfo.notifyChargingComplete}
+            trackColor={this.state.switchStyle}
+            onValueChange={v => this.changeNotifySwitch("notifyChargingComplete", v)}/>
+        </Button>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => this.changeNotifySwitch("notifyLowBalance", !userInfo.notifyLowBalance)}>
+          <Text style={styles.buttonText}>Notify me when wallet is low balance</Text>
+          <Switch 
+            value={this.state.userInfo.notifyLowBalance}
+            trackColor={isIOS ? { false: "#B2B2B2", true: colorAccent } : null}
+            onValueChange={v => this.changeNotifySwitch("notifyLowBalance", v)}/>
+        </Button>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => this.changeNotifySwitch("notifyPromotionsOffers", !userInfo.notifyPromotionsOffers)}>
+          <Text style={styles.buttonText}>Notify me for promotions and offers</Text>
+          <Switch
+            value={this.state.userInfo.notifyPromotionsOffers}
+            trackColor={isIOS ? { false: "#B2B2B2", true: colorAccent } : null}
+            onValueChange={v => this.changeNotifySwitch("notifyPromotionsOffers", v)}/>
+        </Button>
         <Text style={styles.title}>Maps</Text>
         <Button
           style={styles.itemButton}
@@ -88,21 +150,21 @@ export default class Settings extends Component {
           <Text style={styles.buttonText}>Always show my locations</Text>
           <Switch
             value={this.state.settings.alwaysLocation}
-            trackColor={isIOS ? { false: "#B2B2B2", true: colorAccent } : null}
-            onValueChange={v => {
-              this.changeSwitch('alwaysLocation', v);
-            }}/>
+            trackColor={this.state.switchStyle}
+            onValueChange={v => this.changeSwitch('alwaysLocation', v)}/>
         </Button>
         <View style={{height: 56}}>
-          <View style={styles.dropdownView}>
+          <Pressable style={styles.dropdownView} android_ripple={ripple}>
             <Text style={styles.buttonText}>Auto refresh interval</Text>
             <Text style={styles.settingText}>{this.state.settings.refreshInterval}s</Text>
-          </View>
+          </Pressable>
           <Dropdown
             style={styles.hideItem}
             list={this.state.intervalList}
             title="Refresh interval"
             suffixList="s"
+            showText={false}
+            showIcon={false}
             value={this.state.settings.refreshInterval}
             rippleStyle={ripple}
             onChange={v => this.changeSettings("refreshInterval", v)}
@@ -119,11 +181,11 @@ const styles = StyleSheet.create({
     color: '#888',
     fontSize: 12,
     ...$padding(8, 16, 4),
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   itemButton: {
     borderRadius: 0,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   itemView: {
     flex: 1,
@@ -135,7 +197,7 @@ const styles = StyleSheet.create({
   },
   buttonText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 16
   },
   settingText: {
@@ -152,12 +214,11 @@ const styles = StyleSheet.create({
     position: 'absolute',
     alignItems: 'center',
     flexDirection: 'row',
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   hideItem: {
     flex: 1,
     height: 56,
-    opacity: 0,
     paddingLeft: 16,
     paddingRight: 16,
     alignItems: 'center',

+ 1 - 1
Strides-APP/app/pages/charge/Details.js

@@ -7,7 +7,7 @@ import { Image, Pressable, RefreshControl, ScrollView, StyleSheet, Text, Touchab
 import Modal from 'react-native-modal';
 import apiStation from '../../api/apiStation';
 import { ModalProps } from '../../components/BottomModal';
-import { MyRefreshProps } from '../../components/MyRefreshControl';
+import { MyRefreshProps } from '../../components/ThemesConfig';
 import { StationBack } from '../../components/Toolbar';
 import utils from '../../utils/utils';
 import Charge from './Charge';

+ 1 - 1
Strides-APP/app/pages/charge/Summary.js

@@ -7,7 +7,7 @@ import { View, Text, StyleSheet, Image, ScrollView, RefreshControl } from 'react
 import apiCharge from '../../api/apiCharge';
 import Button from '../../components/Button';
 import Dialog from '../../components/Dialog';
-import { MyRefreshProps } from '../../components/MyRefreshControl';
+import { MyRefreshProps } from '../../components/ThemesConfig';
 import utils from '../../utils/utils';
 import { PageList } from '../Router';
 import { ChargeStyle, TypeImage } from './Charging';

+ 277 - 0
Strides-APP/app/pages/chargeV2/ChargeAdapter.js

@@ -0,0 +1,277 @@
+/**
+ * 充电桩ViewPager适配器
+ * @邠心vbe on 2023/02/06
+ */
+import React, { Component } from 'react';
+import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';
+import { BackHandler, Pressable, RefreshControl, ScrollView, StyleSheet } from 'react-native';
+import Charge from './TabCharge';
+import Reserve from './TabReserve';
+import TabInfos from './TabInfos';
+import { MyRefreshProps } from '../../components/ThemesConfig';
+import { QRResult } from '../charge/QRScan';
+import apiStation from '../../api/apiStation';
+import PagerUtil from './PagerUtil';
+import utils from '../../utils/utils';
+import { Styles } from '../../components/Toolbar';
+import { getFocusedRouteNameFromRoute } from '@react-navigation/core';
+
+export const PagerList = {
+  "tabInfo": "Info",
+  "tabCharge": "Charge",
+  "tabReserve": "Reserve",
+  "tabExplore": "Explore"
+}
+
+export default class ChargeAdapter extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      showTab: true,
+      refreshing: false,
+      showContent: false,
+      showLoginDialog: false,
+      stationInfo: {}
+    }
+    this.pageAdapter = [{
+      title: "Info",
+      component: TabInfos
+    }, {
+      title: "Charge",
+      component: Charge
+    }/*, {
+      title: "Reserve",
+      component: Reserve
+    }/*, {
+      title: "Explore",
+      component: Reserve
+    }*/]
+    this.tabBarStyle = {
+      style: styles.tabStyle,
+      pressColor: rippleColor,
+      scrollEnabled: this.pageAdapter.length > 3,
+      indicatorStyle: styles.indicator,
+      activeTintColor: textPrimary,
+      inactiveTintColor: textSecondary //"#E0E0E0", 
+    }
+    this.isHide = false;
+    this.titleName = "";
+  }
+
+  componentDidMount() {
+    this.props.navigation.addListener('beforeRemove', (e) => {
+      this.hideDialog();
+    });
+    this.props.navigation.addListener('focus', () => {
+      if (this.isHide && this.state.showContent) {
+        this.updateStationInfo();
+      } else {
+        //this.canShowLoginDialog();
+      }
+      this.isHide = false;
+    });
+    this.props.navigation.addListener('blur', () => {
+      this.isHide = true;
+    });
+    const params = this.props.route.params;
+    if (params.action && params.stationInfo) {
+      this.action = params.action
+      this.setState({
+        stationInfo: params.stationInfo
+      }, () => {
+        this.setPageTitle();
+        this.canShowLoginDialog();
+      });
+    }
+    BackHandler.addEventListener('hardwareBackPress', this.backPage)
+    PagerUtil.addOnRefresh(this);
+    // setTimeout(() => {
+    //   this.changeTab(1)
+    // }, 200);
+  }
+
+  componentDidUpdate() {
+    /*if (this.state.tabIndex !== this.props.route.state.index) {
+      this.setState({
+        tabIndex: this.props.route.state.index
+      })
+    }*/
+    var act = this.props.route.params.action;
+    if (act !== this.state.action) {
+      this.setState({
+        action: act
+      });
+      if (act == 'qr') {
+        //this.onEnterStation();
+      }
+    }
+  }
+
+  componentWillUnmount() {
+    QRResult.clearResult();
+    PagerUtil.onDestory();
+    BackHandler.removeEventListener("hardwareBackPress", this.backPage)
+  }
+
+  backPage = () => {
+    const params = this.props.route.params;
+    if (params.from && !this.isHide) {
+      startPage(params.from);
+      return true;
+    }
+  }
+
+  canShowLoginDialog() {
+    if (isLogin()) {
+      this.setState({
+        showContent: true
+      });
+      //if (this.action !== 'view')
+      this.updateStationInfo();
+      if (this.action == 'qr') {
+        // setTimeout(() => {
+        //   this.changeTab(1)
+        // }, 300);
+      }
+    } else {
+      this.setState({
+        showLoginDialog: true
+      });
+    }
+  }
+
+  hideDialog(close) {
+    this.setState({
+      showLoginDialog: false
+    });
+    if (close) {
+      goBack();
+    }
+  }
+
+  updateStationInfo() {
+    navigator.geolocation.getCurrentPosition(location => {
+      let params = {
+        lat: location.coords.latitude,
+        lng: location.coords.longitude,
+        sitePk: this.state.stationInfo.id
+      }
+      apiStation.getStationRate(params).then(res => {
+        if (res.data.sitePk) {
+          var info = utils.getSiteInfo(res.data);
+          PagerUtil.setStationInfo(info);
+          PagerUtil.setRefreshing(getFocusedRouteNameFromRoute(this.props.route));
+          this.setState({
+            refreshing: false,
+            stationInfo: info
+          }, () => {
+            this.setPageTitle();
+          });
+        }
+      }).catch(err => {
+        toastLong(err);
+        this.setState({
+          refreshing: false
+        })
+      });
+    });
+  }
+
+  setPageTitle() {
+    if (!this.titleName) {
+      this.titleName = this.state.stationInfo.name;
+      this.props.navigation.setOptions({
+        headerTitle: this.titleName,
+        headerRight: () => (
+          <Pressable
+            style={Styles.backIcon}
+            android_ripple={rippleLessIcon}
+            onPress={() => utils.directMaps(this.state.stationInfo.latitude, this.state.stationInfo.longitude, this.state.stationInfo.address)}>
+            <MaterialIcons
+              name='directions'
+              size={24}
+              color={colorLight}
+              style={Styles.iconOpacity}
+            />
+          </Pressable>
+        )
+      })
+    }
+  }
+
+  getAvailable(type) {
+    const all = this.state.stationInfo.allConnector;
+    if (all) {
+      if (type == 'box') {
+        return all.boxAvailable + '/' + all.boxAll;
+      } else {
+        return all.available + '/' + all.all;
+      }
+    } else {
+      return '0/0';
+    }
+  }
+
+  onPullRefresh() {
+    this.updateStationInfo();
+  }
+
+  onBackRefresh() {
+    this.onPullRefresh();
+  }
+
+  onEnterStation() {
+    this.changeTab(1);
+  }
+
+  changeTab(index) {
+    this.props.navigation.navigate(this.pageAdapter[index]?.title);
+    /*this.setState({
+      tabIndex: index
+    })*/
+  }
+  
+  render() {
+    const Tab = createMaterialTopTabNavigator();
+    return (
+      <ScrollView
+        style={styles.container}
+        keyboardShouldPersistTaps={'handled'}
+        refreshControl={
+          <RefreshControl
+            {...MyRefreshProps}
+            refreshing={this.state.refreshing}
+            onRefresh={() => this.onPullRefresh()}
+          />
+        }>
+        <Tab.Navigator
+          style={{minHeight: $vht(100)}}
+          tabBarOptions={this.tabBarStyle}
+          lazy={false}
+          lazyPreloadDistance={1}>
+          { this.pageAdapter.map((item, index) => 
+            <Tab.Screen
+              key={index}
+              name={item.title}
+              component={item.component}
+            />
+          )}
+        </Tab.Navigator>
+      </ScrollView>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    backgroundColor: colorLight
+  },
+  tabStyle: {
+    backgroundColor: colorLight //colorPrimary
+  },
+  indicator: {
+    backgroundColor: colorAccent //colorLight
+  }
+})
+ 
+//export const ChargeStyles = styles;

+ 649 - 0
Strides-APP/app/pages/chargeV2/Charging.js

@@ -0,0 +1,649 @@
+/**
+ * 充电中的页面组件
+ * @邠心vbe on 2021/04/13
+ */
+import React, { useRef, useEffect, useState } from 'react';
+import { Animated, View, Easing, StyleSheet, Image, Text, ImageBackground, TextInput } from 'react-native';
+import { RadialGradient } from 'react-native-gradients';
+import Modal from 'react-native-modal';
+import { ModalProps } from '../../components/BottomModal';
+import Button, { ElevationObject, ViewHeight } from '../../components/Button';
+import ChargeItemSelect from '../../icons/ChargeItemSelect';
+import { DialogMaxWidth } from './InfoDialog';
+import { QRResult } from '../charge/QRScan';
+import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
+import utils from '../../utils/utils';
+
+export const circleSize = $vw(50.66) < 300 ? $vw(50.66) : 300;
+
+const batterySize = 0.463 * circleSize;
+const batteryWidth = 0.659*batterySize;
+
+export const TypeImage = {
+  AC: require('../../images/charge/ic-type-ac.png'),
+  DC: require('../../images/charge/ic-type-dc.png'),
+  CHADEMO: require('../../images/charge/ic-type-chademo.png')
+}
+
+export const TypeImageList = [
+  {
+    name: 'AC',
+    key: 'AC',
+    icon: require('../../images/charge/ic-type-ac.png')
+  }, {
+    name: 'DC',
+    key: 'DC',
+    icon: require('../../images/charge/ic-type-dc.png')
+  }, /*{
+    name: 'Chademo',
+    key: 'CHADEMO',
+    icon: require('../../images/charge/ic-type-chademo.png')
+  }*/
+]
+
+export const getConnectTypeByKey = (key) => {
+  for (let item of TypeImageList) {
+    if (item.key == key) {
+      return item;
+    }
+  }
+}
+
+export const CircleAnimate = ({isStart = false}) => {
+  var rotate = useRef(new Animated.Value(0)).current;
+
+  const spins = () => {
+    Animated.timing(rotate, {
+      toValue: 1,
+      duration: 10000,
+      easing: Easing.linear,
+      useNativeDriver: true
+    }).start(() => {
+      rotate.setValue(0);
+      spins();
+    });
+  }
+
+  useEffect(() => {
+    if (isStart) {
+      spins();
+    }
+  }, [rotate]);
+
+  const spin = rotate.interpolate({
+    inputRange: [0, 1],
+    outputRange: ['0deg', '360deg']
+  })
+
+  return (
+    <Animated.View
+      style={[
+        styles.chargeCircle, {
+          transform: [{ 
+            rotate: spin 
+          }]
+        }
+      ]}>
+      { isStart &&
+        <View style={{ width: 25, height: 25, marginTop: -11}}>
+          <RadialGradient
+            x="50%" y="50%" rx="50%" ry="50%"
+            colorList={[
+              {offset: '0%', color: '#FFF', opacity: .8},
+              {offset: '40%', color: '#FFF', opacity: .5},
+              {offset: '70%', color: '#FFF', opacity: .3},
+              {offset: '100%', color: '#FFF',opacity: 0}
+            ]}/>
+        </View>
+      }
+    </Animated.View>
+  );
+}
+
+export const BatteryView = ({soc, isPending, isCharging}) => {
+  var [powerPercent, setPercent] = useState(-1);
+  /*var [powerText, setText] = useState(0);
+
+  const autoCharge = () => {
+    setTimeout(() => {
+      const p = powerPercent + 0.001
+      setPercent(Number(p.toFixed(4)))
+      setText((powerPercent * 100).toFixed(0))
+      if (powerPercent >= 1) {
+        onComplete();
+      }
+    }, 50 + powerPercent * 100);
+  }
+
+  useEffect(() => {
+    if (run && powerPercent <= 1) {
+      autoCharge();
+    }
+  }, [powerPercent])*/
+  useEffect(() => {
+    if (isCharging) {
+      try {
+        var s = '-1' + soc;
+        var d = '' + parseInt(s);
+        d = d.replace('-1', '');
+        if (d) {
+          setPercent(parseInt(d))
+        } else {
+          setPercent(-1);
+        }
+      } catch (e) {
+        setPercent(-1);
+      }
+    }
+  }, [soc]);
+
+  const getOpacity = (unit) => {
+    var op = 1 * (powerPercent + unit);
+    return op < 1 ? op : 1;
+  }
+
+  const getHeight = () => {
+    if (powerPercent > 1) {
+      return batterySize * powerPercent / 100;
+    } else if (powerPercent > 0) {
+      return batterySize * powerPercent;
+    } else {
+      return 0
+    }
+  }
+
+  return (
+    isCharging
+    ? <View style={ui.center}>
+        <ImageBackground
+          style={{
+            width: circleSize,
+            height: circleSize,
+            margin: 32,
+            padding: 32,
+            alignItems: 'center',
+            justifyContent: 'center'
+          }}
+          source={require('../../images/charge/ic-charge-circle.png')}>
+          <View style={styles.chargingView}>
+            <View style={styles.plusLeftView}>
+              <Image
+                style={[styles.plusMiddle, { opacity: getOpacity(0.5)}]}
+                source={require('../../images/charge/ic-plus-middle.png')}/>
+              <Image
+                style={[styles.plusSmall, { opacity: getOpacity(0.4)}]}
+                source={require('../../images/charge/ic-plus-small.png')}/>
+            </View>
+            <View style={styles.batteryView}>
+              <View style={[styles.batteryIcon]}>
+                <Image
+                  style={styles.batteryIcon}
+                  source={require('../../images/charge/ic-battery-0.png')}/>
+                <View style={[styles.batteryLayer, { height: getHeight() }]}>
+                  <Image
+                    style={[styles.batteryIcon]}
+                    source={require('../../images/charge/ic-battery-1.png')}/>
+                </View>
+              </View>
+              { isPending
+                ? <Text style={styles.batterySoc}>Initiating...</Text>
+                : powerPercent != -1
+                  ? <Text style={styles.batteryPercent}>{powerPercent}%</Text>
+                  : <Text style={styles.batterySoc}>{'In Charging'}</Text>
+              }
+            </View>
+            <View style={styles.plusRightView}>
+              <Image
+                style={[styles.plusLarge, { opacity: getOpacity(0.6)}]}
+                source={require('../../images/charge/ic-plus-large.png')}/>
+            </View>
+          </View>
+          <CircleAnimate isStart={true}/>
+        </ImageBackground>
+      </View>
+    : <View style={styles.completeView}>
+        <Image
+          style={styles.disconnectIcon}
+          source={require('../../images/charge/charge-complete.png')}/>
+        <Text style={styles.completeTip}>Please disconnect and return connector to charging station</Text>
+      </View>
+  );
+}
+
+export const DashboardView = ({isCharging=false, connectorInfo={}}) => {
+  if (isCharging) {
+    return (
+      <View style={styles.dashboardStartView}>
+        <View style={styles.dashboardGradientView}>
+          <Svg width="365" height="132" viewBox="0 0 365 132">
+            <Rect width="365" height="132" fill="url(#paint0_linear_4415_6112)"/>
+            <Defs>
+              <LinearGradient id="paint0_linear_4415_6112" x1="169.5" y1="136.5" x2="170.5" y2="5.49999" gradientUnits="userSpaceOnUse">
+                <Stop stopColor="#5BFA00"/>
+                <Stop offset="1" stopColor="#009E81"/>
+              </LinearGradient>
+            </Defs>
+          </Svg>
+        </View>
+        <Text style={styles.dashboardTitleWhite}>Charging In Progress...</Text>
+        <View style={styles.dashboardItemGroup}>
+          <View style={styles.dashboardItemView}>
+            <Text style={styles.dashboardItemValue}>{utils.minutes2HHmm(connectorInfo?.timeElapsed ?? 0)}</Text>
+            <Text
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.dashboardItemTitle}>Time Elapsed</Text>
+          </View>
+          <View style={styles.dashboardItemView}>
+            <Text style={styles.dashboardItemValue}>{connectorInfo?.totalKWhDelivered ?? "0"} kWh</Text>
+            <Text
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.dashboardItemTitle}>Total kWh Delivered</Text>
+          </View>
+          <View style={styles.dashboardItemView}>
+          <Text style={styles.dashboardItemValueWeight}>{currency} {connectorInfo?.totalCharges ?? "0.0"}</Text>
+            <Text
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.dashboardItemTitle}>Total Charges</Text>
+          </View>
+        </View>
+      </View>
+    )
+  } else {
+    return (
+      <View style={styles.dashboardGreyView}>
+        <Text style={styles.dashboardTitle}>Press Start To Begin</Text>
+        <View style={styles.dashboardItemGroup}>
+          <View style={styles.dashboardItemView}>
+            <Text style={styles.dashboardItemValue}>-</Text>
+            <Text
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.dashboardItemTitle}>Time Elapsed</Text>
+          </View>
+          <View style={styles.dashboardItemView}>
+            <Text style={styles.dashboardItemValue}>-</Text>
+            <Text
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.dashboardItemTitle}>Total kWh Delivered</Text>
+          </View>
+          <View style={styles.dashboardItemView}>
+          <Text style={styles.dashboardItemValueWeight}>-</Text>
+            <Text
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.dashboardItemTitle}>Total Charges</Text>
+          </View>
+        </View>
+      </View>
+    )
+  }
+}
+
+export const EnterStationDialog = ({visible, stationId, onConfirm, onClose}) => {
+  var [inputStationId, setInput] = useState('')
+
+  const enterStatioinId= () => {
+    //console.log(inputStationId);
+    if (inputStationId) {
+      QRResult.applyInputStation(inputStationId, stationId, (success, err) => {
+        setInput('')
+        if (success) {
+          if (onConfirm) onConfirm()
+        } else if (err) {
+          toastShort(err)
+        }
+        if (onClose) onClose()
+      });
+    } else {
+      toastShort('Please input Station ID')
+    }
+  }
+
+  return (
+    <Modal
+      isVisible={visible}
+      {...ModalProps}
+      onBackdropPress={() => onClose}
+      onBackButtonPress={() => onClose}>
+      <View style={styles.stationDialog}>
+        <Text style={styles.stationInputTitle}>Enter Station ID</Text>
+        <TextInput
+          style={styles.stationInput}
+          defaultValue={inputStationId}
+          placeholder='e.g: LEMOC0002-1'
+          onChangeText={text => setInput(text)}
+        />
+        <View style={styles.dialogButtons}>
+          <Button
+            textSize={17}
+            style={styles.buttonCancel}
+            viewStyle={ViewHeight(42)}
+            text='Close'
+            textColor={textCancel}
+            onClick={onClose}/>
+          <Button
+            textSize={17}
+            style={styles.buttonOK}
+            viewStyle={ViewHeight(42)}
+            text='Confirm'
+            onClick={() => enterStatioinId()}/>
+        </View>
+      </View>
+    </Modal>
+  )
+}
+
+const styles = StyleSheet.create({
+  chargingView: {
+    width: circleSize,
+    height: circleSize,
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  chargeCircle: {
+    top: 0,
+    left: 0,
+    zIndex: 10,
+    width: circleSize,
+    height: circleSize,
+    position: 'absolute',
+    alignItems: 'center',
+    borderRadius: circleSize
+  },
+  batteryView: {
+    paddingTop: 8,
+    paddingLeft: 12,
+    paddingRight: 12,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  batteryIcon: {
+    width: batteryWidth,
+    height: batterySize,
+    position: 'relative',
+    justifyContent: 'flex-end',
+  },
+  batteryLayer: {
+    left: 0,
+    right: 0,
+    height: 0,
+    width: batteryWidth,
+    overflow: 'hidden',
+    position: 'absolute',
+    justifyContent: 'flex-end',
+  },
+  batteryPercent: {
+    color: textPrimary,
+    fontSize: 22,
+    textAlign: 'center',
+    paddingTop: 10,
+    paddingBottom: 8
+  },
+  batterySoc: {
+    color: textPrimary,
+    fontSize: 18,
+    textAlign: 'center',
+    paddingTop: 10,
+    paddingBottom: 10
+  },
+  plusLeftView: {
+    flex: 1,
+    height: batterySize + 10,
+    marginBottom: 12,
+    alignItems: 'flex-end',
+    justifyContent: 'space-around'
+  },
+  plusRightView: {
+    flex: 1,
+    justifyContent: 'center'
+  },
+  plusLarge: {
+    width: 24,
+    height: 24
+  },
+  plusMiddle: {
+    width: 18,
+    height: 18
+  },
+  plusSmall: {
+    width: 12,
+    height: 12,
+    marginBottom: 8
+  },
+  selectView: {
+    position: 'relative'
+  },
+  selectIcon: {
+    width: 18,
+    height: 18
+  },
+  selectIconAbs: {
+    top: -2,
+    right: -4,
+    zIndex: 2,
+    position: 'absolute'
+  },
+  completeView: {
+    height: circleSize,
+    marginBottom: 16,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  disconnectIcon: {
+    width: 100,
+    height: 100
+  },
+  completeTip: {
+    width: circleSize,
+    color: textPrimary,
+    fontSize: 16,
+    paddingLeft: 16,
+    paddingRight: 16,
+    textAlign: 'center'
+  },
+  stationDialog: {
+    padding: 16,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    borderRadius: isIOS ? 20 : 3,
+    width: DialogMaxWidth,
+    backgroundColor: colorLight
+  },
+  stationInput: {
+    ...$padding(4, 10),
+    minHeight: 40,
+    borderRadius: 3,
+    backgroundColor: '#F0F0F0'
+  },
+  stationInputTitle: {
+    fontSize: 15,
+    textAlign: 'center',
+    paddingBottom: 16
+  },
+  dialogButtons: {
+    paddingTop: 24,
+    paddingBottom: 8,
+    flexDirection: 'row'
+  },
+  buttonOK: {
+    flex: 1,
+    marginLeft: 12,
+  },
+  buttonCancel: {
+    flex: 1,
+    borderWidth: 1,
+    borderColor: colorCancel,
+    backgroundColor: pageBackground
+  },
+  dashboardGreyView: {
+    padding: 16,
+    marginTop: 16,
+    marginBottom: 16,
+    borderRadius: 6,
+    ...ElevationObject(5),
+    backgroundColor: pageBackground
+  },
+  dashboardStartView: {
+    padding: 16,
+    marginTop: 16,
+    marginBottom: 16,
+    borderRadius: 6,
+    ...ElevationObject(5)
+  },
+  dashboardGradientView: {
+    top: 0,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    borderRadius: 6,
+    overflow: 'hidden',
+    position: "absolute",  
+  },
+  dashboardTitle: {
+    color: textPrimary,
+    fontSize: 25,
+    fontWeight: 'bold',
+    textAlign: 'center',
+    paddingBottom: 16
+  },
+  dashboardTitleWhite: {
+    color: textLight,
+    fontSize: 25,
+    fontWeight: 'bold',
+    textAlign: 'center',
+    paddingBottom: 16
+  },
+  dashboardItemGroup: {
+    marginRight: -12,
+    marginBottom: -32,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  dashboardItemView: {
+    flex: 1,
+    marginRight: 12,
+    borderRadius: 6,
+    ...$padding(10, 6, 14),
+    ...ElevationObject(5),
+    backgroundColor: colorLight
+  },
+  dashboardItemTitle: {
+    color: textCancel,
+    fontSize: 12,
+    textAlign: 'center',
+    paddingTop: 2
+  },
+  dashboardItemValue: {
+    color: textPrimary,
+    fontSize: 14,
+    textAlign: 'center'
+  },
+  dashboardItemValueWeight: {
+    color: textPrimary,
+    fontSize: 14,
+    fontWeight: 'bold',
+    textAlign: 'center'
+  }
+});
+
+export const SelectableIcon = ({selected, children}) => {
+  return (
+    <View style={styles.selectView}>
+      {selected &&
+        <View style={[styles.selectIcon, children && styles.selectIconAbs]}>
+          <ChargeItemSelect size={18} selected={true}/>
+        </View>
+      }
+      {children}
+    </View>
+  );
+}
+
+export const ChargeStyle = StyleSheet.create({
+  stationInfoView: {
+    padding: 12,
+    borderRadius: 10,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    ...ElevationObject(5),
+    backgroundColor: colorLight,
+    justifyContent: 'space-between'
+  },
+  infoGroup: {
+    ...$padding(4, 2),
+    alignItems: 'center'
+  },
+  infoTitle: {
+    color: '#999',
+    fontSize: 12,
+    paddingTop: 1
+  },
+  infoText: {
+    color: textPrimary,
+    fontSize: 13,
+    fontWeight: 'bold'
+  },
+  infoBoldNumber: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 3,
+    fontWeight: 'bold'
+  },
+  infoStatus: {
+    fontSize: 14,
+    fontWeight: 'bold'
+  },
+  infoIcon: {
+    width: 38,
+    height: 38
+  },
+  itemDivide: {
+    borderTopWidth: 1,
+    borderTopColor: '#eee'
+  },
+  statusSelected: {
+    color: textPrimary,
+    ...$padding(4, 11),
+    backgroundColor: colorAccent
+  },
+  statusAvailable: {
+    color: '#90DB0A'
+  },
+  statusUnavailable: {
+    color: '#666',
+    fontSize: 12,
+    paddingBottom: 1
+  },
+  statusAuthenticated: {
+    color: '#90DB0A',
+    fontSize: 12,
+    paddingBottom: 1
+  },
+  statusError: {
+    color: '#EF3340',
+    fontSize: 12,
+    paddingBottom: 1
+  },
+  statusWarning: {
+    color: textLight,
+    backgroundColor: colorAccent
+  },
+  rateText: {
+    color: textPrimary,
+    fontSize: 14,
+  },
+  ratePrice: {
+    color: '#000',
+    fontSize: 14,
+  },
+  authText: {
+    color: '#000',
+    fontSize: 12,
+    paddingTop: 2
+  }
+});

+ 329 - 0
Strides-APP/app/pages/chargeV2/InfoDialog.js

@@ -0,0 +1,329 @@
+/**
+ * 充电弹窗组件
+ * @邠心vbe on 2021/04/25
+ */
+import React from "react";
+import { Image, Pressable, StyleSheet, Text, View } from "react-native";
+import Modal from "react-native-modal";
+import { ModalProps } from "../../components/BottomModal";
+import Button from "../../components/Button";
+import { Styles } from "../../components/Toolbar";
+import { PageList } from "../Router";
+import app from "../../../app.json"
+
+export const DialogMaxWidth = $vw(85) > 500 ? 500 : $vw(85);
+
+export const LoginDialog = ({onHide, onClose}) => {
+  return (
+    <View style={styles.dialog}>
+      <Text style={styles.title}>Oops!</Text>
+      <Text style={styles.message}>You need to be registered user of {app.displayName}</Text>
+      <View style={styles.loginTipView}>
+        <Text style={styles.tipText}>Login to use this feature</Text>
+        <Image
+          style={styles.tipImage}
+          source={require('../../images/login-head.png')}/>
+      </View>
+      <Button
+        textSize={17}
+        style={styles.loginButton}
+        viewStyle={styles.loginButtonView}
+        text='Login'
+        onClick={() => {
+          onHide();
+          startPage(PageList.login)
+        }}/>
+      <Pressable
+        style={styles.signUpView}
+        onPress={() => {
+          onHide();
+          startPage(PageList.register, {actionLogin: true});
+        }}>
+        <Text style={styles.signUpText}>Don't have an account?</Text>
+        <Text style={styles.signUpLink}>Sign Up</Text>
+      </Pressable>
+      <Ionicons
+        name='close-outline'
+        size={30}
+        color={'#999'}
+        style={styles.closeIcon}
+        onPress={onClose} />
+    </View>
+  );
+}
+
+export const ErrorDialog = ({visible, code, message, onClose}) => {
+  return (
+    <Modal
+      isVisible={visible}
+      onBackButtonPress={onClose}
+      onBackdropPress={onClose}
+      {...ModalProps}>
+      <View style={styles.dialog}>
+        <Text style={styles.title}>Oops!</Text>
+        <Text style={styles.message}>{message}</Text>
+        <ErrorImage code={code}/>
+        <Button
+          textSize={17}
+          style={styles.loginButton}
+          viewStyle={styles.loginButtonView}
+          text='Okay'
+          onClick={onClose}/>
+        <Text style={{fontSize: 12}}></Text>
+        <Ionicons
+          name='close-outline'
+          size={30}
+          color={'#999'}
+          style={styles.closeIcon}
+          onPress={onClose} />
+      </View>
+    </Modal>
+  );
+}
+
+export const LowCreditDialog = ({visible, onClose}) => {
+  return (
+    <Modal
+      isVisible={visible}
+      onBackButtonPress={onClose}
+      onBackdropPress={onClose}
+      {...ModalProps}>
+      <View style={styles.dialog}>
+        <Text style={styles.title}>Low Credits</Text>
+        <Text style={styles.message}>Your credit is below S$5.</Text>
+        <Text style={styles.message}>{"This charging session will end if there is insufficient credits.\n"}</Text>
+        <View style={$padding(12,0)}>
+          <Button
+            text="Top-Up"
+            onClick={() => onClose(true)}/>
+        </View>
+        <Ionicons
+          name='close-outline'
+          size={30}
+          color={'#999'}
+          style={styles.closeIcon}
+          onPress={() => onClose(false)} />
+      </View>
+    </Modal>
+  );
+}
+
+export const CancelReserveDialog = ({visible, onClose}) => {
+  return (
+    <Modal
+      isVisible={visible}
+      onBackButtonPress={onClose}
+      onBackdropPress={onClose}
+      {...ModalProps}>
+      <View style={styles.dialog}>
+        <Text style={styles.title}>Cancel</Text>
+        <Text style={styles.message}>Are you sure you want to cancel the reservation?</Text>
+        <View style={styles.loginTipView}>
+          <Text style={styles.tipText}></Text>
+          <Image
+            style={styles.tipImage}
+            source={require('../../images/login-head.png')}/>
+        </View>
+        <View style={ui.flex}>
+          <Button
+            textSize={17}
+            style={ui.flex1}
+            text='Confirm'
+            onClick={() => onClose(true)}/>
+          <Button
+            textSize={17}
+            style={styles.cancelCloseBtn}
+            text='Close'
+            onClick={() => onClose(false)}/>
+        </View>
+        <Pressable
+          style={styles.closeIcon}
+          android_ripple={rippleLess}
+          onPress={() => onClose(false)}>
+          <Ionicons
+            name='close-outline'
+            size={30}
+            color={'#999'} />
+        </Pressable>
+      </View>
+    </Modal>
+  );
+}
+
+export const RegisterDialog = ({address, onClose}) => {
+  return (
+    <View style={styles.dialog}>
+      <View style={ui.center}>
+        <Text style={styles.regTitleText}>Thank you for registering with</Text>
+        <Image
+          source={require('../../images/app-logo.png')}
+          style={Styles.logo}
+          resizeMode='contain'
+        />
+        <Text style={styles.regMessageText}>a confirmation email has been sent to</Text>
+        <Text style={styles.regMessageLink}>{address}</Text>
+      </View>
+      <View style={$padding(12,0)}>
+        <Button
+          text="Back to Login"
+          onClick={onClose}/>
+      </View>
+      <Pressable
+        style={styles.closeIcon}
+        android_ripple={rippleLess}
+        onPress={onClose}>
+        <Ionicons
+          name='close-outline'
+          size={30}
+          color={'#999'}/>
+      </Pressable>
+    </View>
+  );
+}
+
+const ErrorImage = ({code}) => {
+  switch (code) {
+    case 'A1':
+      return (
+        <Image
+          style={styles.errorImage}
+          source={require('../../images/site/error-A1.jpg')}/>
+      );
+    case 'A4':
+      return (
+        <Image
+          style={styles.errorImage}
+          source={require('../../images/site/error-A4.jpg')}/>
+      );
+    case 'A5':
+      return (
+        <Image
+          style={styles.errorImage}
+          resizeMode="contain"
+          source={require('../../images/site/error-A5.jpg')}/>
+      );
+    case 'A9':
+      return (
+        <Image
+          style={styles.errorImage}
+          resizeMode="contain"
+          source={require('../../images/site/error-A9.jpg')}/>
+      );
+    default:
+      return (
+        <Image
+          style={styles.errorImage}
+          resizeMode="contain"
+          source={require('../../images/site/error-A9.jpg')}/>
+      );
+  }
+}
+
+const styles = StyleSheet.create({
+  dialog: {
+    width: DialogMaxWidth,
+    paddingTop: 16,
+    paddingLeft: 20,
+    paddingRight: 20,
+    paddingBottom: 16,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    borderRadius: isIOS ? 20 : 3,
+    backgroundColor: colorLight
+  },
+  title: {
+    color: '#000',
+    fontSize: 24,
+    textAlign: "center"
+  },
+  message: {
+    zIndex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    lineHeight: 22,
+    paddingTop: 16,
+    paddingLeft: 32,
+    paddingRight: 32,
+    paddingBottom: 2,
+    textAlign: "center"
+  },
+  loginTipView: {
+    zIndex: 1,
+    marginTop: -18,
+    paddingLeft: 12,
+    paddingRight: 12,
+    alignItems: 'flex-end',
+    flexDirection: "row"
+  },
+  tipText: {
+    flex: 1,
+    color: '#999',
+    fontSize: 12,
+    paddingBottom: 12
+  },
+  tipImage: {
+    width: 96.6,
+    height: 83.4
+  },
+  loginButton: {
+    marginTop: -2,
+    borderRadius: 10
+  },
+  loginButtonView: {
+    flex: 1,
+    height: 54,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  signUpView: {
+    color: textPrimary,
+    paddingTop: 16,
+    flexDirection: "row",
+    alignItems: "center",
+    justifyContent: "center"
+  },
+  signUpText: {
+    color: textPrimary,
+    fontSize: 12
+  },
+  signUpLink: {
+    fontSize: 12,
+    padding: 8,
+    ...ui.link,
+    textDecorationLine: "underline"
+  },
+  closeIcon: {
+    top: 12,
+    right: 12,
+    position: "absolute"
+  },
+  errorImage: {
+    width: DialogMaxWidth * 0.9,
+    height: DialogMaxWidth * 0.2,
+    marginTop: 4,
+    marginLeft: 2,
+    marginBottom: -6
+  },
+  regTitleText: {
+    color: '#000',
+    fontSize: 20,
+    ...$padding(32, 0, 16),
+    textAlign: 'center'
+  },
+  regMessageText: {
+    color: '#000',
+    fontSize: 14,
+    paddingTop: 24,
+    lineHeight: 20
+  },
+  regMessageLink: {
+    ...ui.link,
+    fontSize: 14,
+    paddingBottom: 16
+  },
+  cancelCloseBtn: {
+    flex: 1,
+    marginLeft: 16,
+    backgroundColor: '#ddd'
+  }
+})

+ 44 - 0
Strides-APP/app/pages/chargeV2/PagerUtil.js

@@ -0,0 +1,44 @@
+import { PagerList } from "./ChargeAdapter";
+
+var chargeInfoState = global.chargeInfoState
+var refreshListener = [];
+
+export default PagerUtil = {
+  getStationInfo: () => {
+    return chargeInfoState.stationInfo ?? {}
+  },
+  setStationInfo: (info) => {
+    chargeInfoState.stationInfo = info;
+  },
+  addOnRefresh: (page) => {
+    refreshListener.push(page)
+  },
+  setRefreshing: (route) => {
+    console.log("刷新子页面", route);
+    refreshListener.map((item, index) => {
+      if (!route || route == item.props?.route?.name) {
+        if (item.onRefresh)
+          item.onRefresh();
+      }
+    })
+  },
+  setBackRefreshing: () => {
+    refreshListener.map((item, index) => {
+      if (item.onBackRefresh)
+        item.onBackRefresh();
+    })
+  },
+  onCharge: () => {
+    startPage(PagerList.tabCharge);
+  },
+  onReserve: () => {
+    startPage(PagerList.tabReserve);
+  },
+  onEnterStation: () => {
+    startPage(PagerList.tabCharge);
+  },
+  onDestory: () => {
+    chargeInfoState = {};
+    refreshListener = [];
+  }
+}

+ 64 - 0
Strides-APP/app/pages/chargeV2/Payment.js

@@ -0,0 +1,64 @@
+import React, { Component } from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
+import { PAYTYPE } from '../wallet/Payment';
+import { PaymentIcon } from '../wallet/TopupPaythod';
+import { ChargeStyle } from './Charging';
+
+export const CHARGE_PAYTYPE = [{
+  // 按次支付
+  name: "Pay Per Use",
+  type: PAYTYPE.PAY_PER_USE,
+  title: "SGQR",
+  icon: "PAYNOW"
+}, {
+  // 钱包余额支付
+  name: "Credit Wallet",
+  type: PAYTYPE.CREDIT_WALLET,
+  icon: "WALLET",
+  balance: true
+}]
+
+export const PaymentList = ({isSelect=true, payType, payPerUse, onMethodChange}) => (
+  CHARGE_PAYTYPE.map((item, index) => {
+    if (isSelect || payType==item.type) {
+      return (
+        <BadgeSelectItem
+          key={index}
+          style={ChargeStyle.stationInfoView}
+          checked={payType==item.type}
+          onPress={() => {
+            if (onMethodChange) {
+              onMethodChange(item.type);
+            }
+          }}>
+          <PaymentIcon 
+            method={item.icon}
+            checked={payType==item.type}/>
+          <View style={styles.paymentView}>
+            <Text style={styles.valueText}>{item.balance ? (currency + userInfo.credit) : item.title}</Text>
+            <Text style={styles.paymentText}>{item.name}</Text>
+          </View>
+        </BadgeSelectItem>
+      )
+    } else {
+      return null
+    }
+  })
+)
+
+const styles = StyleSheet.create({
+  paymentView: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  valueText: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  paymentText: {
+    color: textSecondary,
+    fontSize: 12
+  }
+})

+ 180 - 0
Strides-APP/app/pages/chargeV2/QRScan.js

@@ -0,0 +1,180 @@
+/**
+ * 扫描二维码
+ * @邠心vbe on 2021/03/24
+ */
+import React, { Component } from 'react'
+import { Image, StyleSheet, Text, View } from 'react-native'
+import QRCodeScanner from 'react-native-qrcode-scanner';
+import { RNCamera } from 'react-native-camera';
+import { Styles } from '../../components/Toolbar';
+import apiCharge from '../../api/apiCharge';
+import { PageList } from '../Router';
+import Dialog from '../../components/Dialog';
+
+export const QRResult = {
+  haveResult: () => {
+    return global.QrCodeResult && global.QrCodeResult.connectorPk;
+  },
+  setResult: (info) => {
+    global.QrCodeResult = info;
+  },
+  getResult: () => {
+    return global.QrCodeResult;
+  },
+  clearResult: () => {
+    global.QrCodeResult = {};
+  },
+  applyInputStation: (text, sitePk, back) => {
+    if (text.indexOf('-') > 0) {
+      const arr = text.split('-');
+      if (arr.length >= 2) {
+        let bid = '', cid = '';
+        for (let i = 0; i < arr.length; i++) {
+          if (i == (arr.length-1)) {
+            cid = arr[i];
+          } else {
+            if (i > 0) {
+              bid += '-';
+            }
+            bid += arr[i];
+          }
+        }
+        const qr = {
+          sitePk: sitePk,
+          chargeBoxId: bid,
+          connectorId: cid
+        }
+        console.log('====================================');
+        console.log(qr);
+        console.log('====================================');
+        Dialog.showProgressDialog();
+        apiCharge.checkQRStatus(qr).then(res => {
+          Dialog.dismissLoading();
+          if (res.data && res.data.chargeBoxId) {
+            QRResult.setResult(res.data);
+            back(true)
+          }
+        }).catch(err => {
+          Dialog.dismissLoading();
+          back(false, '')
+          Dialog.showDialog({
+            title: 'Error',
+            message: err,
+            showCancel: false
+          });
+        })
+      } else {
+        back(false, 'Station ID is incorrect')
+      }
+    } else {
+      back(false, 'Station ID is incorrect')
+    }
+  }
+}
+
+export default class QRScan extends Component {
+
+  constructor(props) {
+    super(props);
+    this.state={
+      isResult: true,
+      params: this.props.route.params
+    }
+  }
+
+  componentDidMount() {
+    //console.log(this.state.params);
+    setTimeout(() => {
+      this.setState({
+        isResult: false
+      });
+    }, 300);
+  }
+
+  scanResult = (msg) => {
+    this.setState({
+      isResult: true
+    });
+    console.log("result2", msg);
+    if (msg.data.indexOf('::') > 0) {
+      const arr = msg.data.split('::');
+      if (arr.length == 2) {
+        const qr = {
+          chargeBoxId: arr[0],
+          connectorId: arr[1]
+        }
+        if (this.state.params.id) {
+          qr.sitePk = this.state.params.id
+        }
+        this.getChargeDetail(qr);
+        return;
+      }
+    }
+    Dialog.showDialog({
+      title: 'Error',
+      message: 'It\'s not a legal QR code',
+      showCancel: false,
+      callback: (e) => {
+      this.setState({
+        isResult: false
+      });
+    }});
+  }
+
+  getChargeDetail(qr) {
+    apiCharge.checkQRStatus(qr).then(res => {
+      if (res.data && res.data.chargeBoxId) {
+        QRResult.setResult(res.data);
+        if (res.data.sitePk) {
+          if (this.state.params.actionDetail) {
+            startPage(PageList.chargeDetailPage, {stationInfo: {id: res.data.sitePk}, action: 'qr', from: PageList.home});
+          } else {
+            goBack();
+          }
+          //startPage(PageList.chargeDetail, {stationInfo: {id: res.data.sitePk}, action: 'qr'});
+        }
+      }
+    }).catch(err => {
+      Dialog.showDialog({
+        title: 'Error',
+        message: err,
+        showCancel: false,
+        callback: (e) => {
+        this.setState({
+          isResult: false
+        });
+      }});
+    })
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        { !this.state.isResult
+          ? <QRCodeScanner
+              fadeIn={false}
+              onRead={this.scanResult}
+              reactivate={false}
+              reactivateTimeout={1000}
+              cameraStyle={{ width: $width, height: $vh(100)}}
+              containerStyle={{ width: $width, height: $vh(100)}}
+              flashMode={RNCamera.Constants.FlashMode.off}
+              checkAndroid6Permissions={true} />
+          : <Image
+              style={Styles.logo}
+              source={require('../../images/app-logo.png')}/>
+        }
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: '#242B32',
+    ...StyleSheet.absoluteFillObject
+  }
+})
+

+ 304 - 0
Strides-APP/app/pages/chargeV2/Summary.js

@@ -0,0 +1,304 @@
+/**
+ * 新版充电结算页面
+ * @邠心vbe on 2023/02/08
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, Image, ScrollView, RefreshControl } from 'react-native';
+import apiCharge from '../../api/apiCharge';
+import Button from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import { MyRefreshProps } from '../../components/ThemesConfig';
+import utils from '../../utils/utils';
+import { PageList } from '../Router';
+import { ChargeStyle, TypeImage } from './Charging';
+
+export default class Summary extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isActully: true,
+      refreshing: false,
+      summaryInfo: {},
+      stationInfo: {} 
+    };
+    this.canBack = false;
+  }
+
+  componentDidMount() {
+    const params = this.props.route.params;
+    if (params.chargingPk) {
+      Dialog.showProgressDialog();
+      if (params.name && params.address) {
+        this.setState({
+          chargingPk: params.chargingPk,
+          stationInfo: {
+            id: params.id,
+            name: params.name,
+            address: params.address
+          }
+        });
+        setTimeout(() => {
+          this.getSummaryData(params.chargingPk);
+        }, 5000);
+      } else if (params.action && params.action == "view") {
+        this.setState({
+          isActully: false,
+          chargingPk: params.chargingPk
+        });
+        this.getSummaryData(params.chargingPk);
+      }
+    }
+    this.props.navigation.addListener('focus', e => {
+      this.canBack = false;
+    });
+    /*this.props.navigation.addListener('beforeRemove', e => {
+      if (this.state.isActully && !this.canBack) {
+        this.toRating();
+        e.preventDefault();
+      }
+    });*/
+  }
+
+  getSummaryData(chargingPk) {
+    apiCharge.getChargeSummary({
+      chargingPk: chargingPk
+    }).then(res => {
+      Dialog.dismissLoading();
+      if (res.data) {
+        this.setState({
+          refreshing: false,
+          summaryInfo: res.data
+        });
+      }
+    }).catch(err => {
+      Dialog.dismissLoading();
+      toastShort(err);
+      this.setState({
+        refreshing: false
+      });
+    });
+  }
+
+  onRefresh() {
+    if (this.state.chargingPk) {
+      this.setState({
+        refreshing: true
+      });
+      this.getSummaryData(this.state.chargingPk);
+    }
+  }
+
+  toRating() {
+    this.canBack = true;
+    goBack();
+    //startPage(PageList.rating, this.state.stationInfo);
+  }
+
+  getSummaryText(data) {
+    if (this.state.summaryInfo?.paymentType == 'Fleet Credit') {
+      return '-';
+    } else {
+      return currency + '' + (data ?? '0');
+    }
+  }
+
+  render() {
+    return (
+      <ScrollView
+        style={styles.container}
+        refreshControl={
+          <RefreshControl
+            {...MyRefreshProps}
+            refreshing={this.state.refreshing}
+            onRefresh={() => this.onRefresh()}
+          />
+        }>
+        <View style={{height:16}}></View>
+        <View style={styles.sections}>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Transation ID:</Text>
+            <Text style={styles.text}>{this.state.summaryInfo.transactionPk}</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Reference ID:</Text>
+            <Text style={styles.text}>{this.state.summaryInfo.chargingPk}</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Date Time:</Text>
+            <Text style={styles.text}>{this.state.summaryInfo.dateTime}</Text>
+          </View>
+        </View>
+        <View style={styles.sections}>
+          <Text style={styles.formTitle}>Your Station</Text>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Station ID: {this.state.summaryInfo.chargeBoxPk}</Text>
+          </View>
+          <Text style={styles.stationInfoText}>{this.state.summaryInfo.boxAddress}</Text>
+        </View> 
+
+        <View style={styles.sections2}>
+          <Text style={styles.formTitle}>Your Connector</Text>
+          <View style={styles.stationInfoView}>
+            <Image
+              style={ChargeStyle.infoIcon}
+              source={this.state.summaryInfo.connectorType?.indexOf('AC') >= 0 ? TypeImage.AC : TypeImage.DC}/>
+            <View style={ChargeStyle.infoGroup}>
+              <Text style={ChargeStyle.infoText}>{this.state.summaryInfo.connectorType}</Text>
+              <Text style={ChargeStyle.infoTitle}>Type</Text>
+            </View>
+            <View style={ChargeStyle.infoGroup}>
+              <Text style={ChargeStyle.infoText}>{this.state.summaryInfo.connectorWattage}</Text>
+              <Text style={ChargeStyle.infoTitle}>Power</Text>
+            </View>
+            <View style={ChargeStyle.infoGroup}>
+              <Text style={ChargeStyle.infoText}>{this.state.summaryInfo.connectorRate}</Text>
+              <Text style={ChargeStyle.infoTitle}>Rates</Text>
+            </View>
+          </View>
+        </View> 
+
+        <View style={styles.sections}>
+          <Text style={styles.formTitle}>Breakdown</Text>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Reservation Fee:</Text>
+            <Text style={styles.text}>{currency}{this.state.summaryInfo.reservationFee ?? 0}</Text>
+          </View>
+          { utils.isNotEmpty(this.state.summaryInfo.idleFee) &&
+            <View style={styles.formRow}>
+              <Text style={styles.label}>Idle Fee:</Text>
+              <Text style={styles.text}>{currency}{this.state.summaryInfo.idleFee}</Text>
+            </View>
+          }
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Charge Time:</Text>
+            <Text style={styles.text}>{utils.hour2HHmm(this.state.summaryInfo.chargeTime)}</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Charge Delivered:</Text>
+            <Text style={styles.text}>{this.state.summaryInfo.chargeDelivered ?? 0}kWh</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Charge Rates (GST Inclusive):</Text>
+            <Text style={styles.text}>{currency}{this.state.summaryInfo.chargeRates ?? '0.0'}</Text>
+          </View>
+        </View>
+
+        <View style={styles.sections}>
+          <Text style={styles.formTitle}>Subtotal</Text>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Payment Made By:</Text>
+            <Text style={styles.text}>{this.state.summaryInfo.paymentType}</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Previous Balance:</Text>
+            <Text style={styles.text}>{this.getSummaryText(this.state.summaryInfo.previousBalance)}</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Payment (GST Inclusive):</Text>
+            <Text style={styles.text}>{currency}{this.state.summaryInfo.payment ?? '0.0'}</Text>
+          </View>
+          <View style={styles.formRow}>
+            <Text style={styles.label}>Resulting Balance:</Text>
+            <Text style={styles.text}>{this.getSummaryText(this.state.summaryInfo.resultingBalance)}</Text>
+          </View>
+        </View>
+        { this.state.isActully &&
+          <View style={styles.bottomButton}>
+            <Text
+              style={styles.feedback}
+              onPress={() => startPage(PageList.feedback)}>Submit Your Feedback</Text>
+            <Text style={styles.tipText}>A Receipt Will Be Sent To Your Email</Text>
+            <Button
+              text='Done'
+              elevation={1.5}
+              onClick={() => this.toRating()}/>
+          </View>
+        }
+      </ScrollView>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    paddingLeft: 16,
+    paddingRight: 16,
+    backgroundColor: colorLight
+  },
+  sections: {
+    borderRadius: 10,
+    marginBottom: 16,
+    backgroundColor: colorLight
+  },
+  sections2: {
+    paddingTop: 0,
+    paddingLeft: 0,
+    paddingRight: 0,
+    paddingBottom: 8,
+    borderRadius: 10,
+    marginBottom: 8,
+    backgroundColor: colorLight
+  },
+  formTitle: {
+    color: '#000',
+    fontSize: 14,
+    marginTop: -4,
+    marginBottom: 6,
+    paddingBottom: 6,
+    fontWeight: 'bold',
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee'
+  },
+  formRow: {
+    paddingTop: 3,
+    paddingBottom: 3,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  label: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 13,
+  },
+  text: {
+    color: textPrimary,
+    fontSize: 13,
+  },
+  stationInfoView: {
+    padding: 12,
+    marginBottom: 0,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'space-between'
+  },
+  stationInfoText: {
+    color: '#999',
+    fontSize: 11
+  },
+  connectorView: {
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  typeIcon: {
+    width: 36,
+    height: 36
+  },
+  feedback: {
+    color: '#12A5F9',
+    fontSize: 14,
+    textAlign: 'center',
+    marginBottom: 16,
+    ...ui.underline
+  },
+  bottomButton: {
+    marginTop: 32,
+    marginBottom: 16
+  },
+  tipText: {
+    color: textPrimary,
+    fontSize: 12,
+    textAlign: 'center',
+    marginBottom: 8
+  }
+});

+ 812 - 0
Strides-APP/app/pages/chargeV2/TabCharge.js

@@ -0,0 +1,812 @@
+/**
+ * 新版充电页面
+ * @邠心vbe on 2023/02/06
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, ImageBackground, Image, ScrollView } from 'react-native';
+import apiCharge from '../../api/apiCharge';
+import Button from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import { PageList } from '../Router';
+import { BatteryView, ChargeStyle, circleSize, DashboardView, EnterStationDialog, TypeImage } from './Charging';
+import Payment, { PaymentDefault, PAYTYPE } from '../wallet/Payment';
+import { QRResult } from '../charge/QRScan';
+import { ErrorDialog } from './InfoDialog';
+import utils from '../../utils/utils';
+import PagerUtil from './PagerUtil';
+import { PaymentList } from './Payment';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
+
+export default class TabCharge extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      available: false,
+      isPrivate: false,
+      refreshId: 0,
+      isStart: false,
+      isPending: false,
+      isCharging: false,
+      isAuthentic: false,
+      selectRate: '',
+      connectorInfo: {},
+      stationInfo: {},
+      lastUpdated: '',
+      errorCode: 'A9',
+      errorMessage: '',
+      showErrorDialog: false,
+      showStationDialog: false,
+      curerntPerUser: undefined,
+      //currentPayment: PAYTYPE.CREDIT_WALLET,
+      //currentPaytype: "Credit Wallet",
+      currentPayment: PaymentDefault.DEFAULT.payType,
+      currentPaytype: PaymentDefault.DEFAULT.payName
+    };
+    this.changeMethod = false;
+    this.canAutoRefresh = false;
+    this.inputStationId = '';
+  }
+
+  componentDidMount() {
+    this.canAutoRefresh = true;
+    PagerUtil.addOnRefresh(this);
+    this.onRefresh();
+  }
+
+  onRefresh() {
+    console.log("Charge刷新", this.props.route.name);
+    const info = PagerUtil.getStationInfo();
+    this.setState({
+      stationInfo: info
+    }, () => {
+      this.init();
+      //console.log("站点信息", this.state.stationInfo);
+      //this.checkIsCharge();
+    });
+  }
+
+  init() {
+    console.log("Charge刷新", "init");
+    this.onMethodChanged();
+    this.refreshAvailable();
+    if (QRResult.haveResult()) {
+      console.log("Charge刷新", "haveResult");
+      const info = QRResult.getResult()
+      console.log('QRResult', info);
+      this.setState({
+        isAuthentic: true,
+        connectorInfo: info
+        //soc: info.chargeType == 'AC' ? 0 : 'In Charging'
+      });
+      this.checkChargeStatus();
+    } else if (this.state.isStart) {
+      console.log("Charge刷新", "isStart");
+      this.checkIsCharge();
+    } else {
+      console.log("Charge刷新", "noStart");
+      this.getConnectorInfo();
+      //this.checkChargeStatus();
+    }
+  }
+
+  componentWillUnmount() {
+    this.canAutoRefresh = false;
+  }
+
+  //刷新可用充电接口
+  refreshAvailable() {
+    const info = this.state.stationInfo
+    const all = info?.allConnector;
+    if (info.siteType == 'Private') {
+      this.setState({
+        isPrivate: true
+      })
+    }
+    if (all) {
+      this.setState({
+        available: !all.available > 0
+      });
+    }
+  }
+
+  enterStatioinId() {
+    if (QRResult.haveResult()) {
+      const info = QRResult.getResult()
+      console.log('EnterResult', info);
+      this.setState({
+        isAuthentic: true,
+        connectorInfo: info
+        //soc: info.chargeType == 'AC' ? 0 : 'In Charging'
+      });
+      this.checkChargeStatus();
+    }
+  }
+
+  onPaymentMethodChanged(payment) {
+    this.setState({
+      currentPayment: payment
+    })
+  }
+
+  onMethodChange() {
+    this.changeMethod = true;
+    startPage(PageList.paymentMethod, {info: this.state.connectorInfo, type: this.state.currentPayment});
+  }
+
+  onMethodChanged() {
+    if (this.changeMethod) {
+      this.changeMethod = false;
+      if (global.paymentOption?.title) {
+        this.setState({
+          curerntPerUser: global.paymentOption.amount,
+          currentPayment: global.paymentOption.value,
+          currentPaytype: global.paymentOption.title
+        }, () => {
+          global.paymentOption= {}
+        })
+      }
+    }
+  }
+
+  //扫码之前和扫码之后的站点信息Section
+  StationInfo() {
+    return (
+      <BadgeSelectItem
+        style={ChargeStyle.stationInfoView}
+        checked={true}>
+        {/* <ImageBackground
+          style={{
+            width: 42,
+            height: 42
+          }}
+          source={require('../../images/charge/icon-station-no.png')}>
+          <Text style={{
+            left: 0,
+            right: 0,
+            bottom: 1,
+            fontSize: 8,
+            color: 'white',
+            textAlign: 'center',
+            position: 'absolute'
+          }}>{this.state.connectorInfo.connectorId}</Text>
+        </ImageBackground> */}
+        <Image
+          style={ChargeStyle.infoIcon}
+          source={this.state.connectorInfo.chargeType.indexOf('AC') >= 0 ? TypeImage.AC : TypeImage.DC}/>
+        <View style={ChargeStyle.infoGroup}>
+          <Text
+            numberOfLines={1}
+            ellipsizeMode="tail"
+            style={ChargeStyle.infoText}>{this.state.connectorInfo.chargeType}{this.state.connectorInfo.wattage}</Text>
+          <Text style={ChargeStyle.infoTitle}>Type</Text>
+        </View>
+        <View style={ChargeStyle.infoGroup}>
+          <Text
+            numberOfLines={1}
+            ellipsizeMode="tail"
+            style={ChargeStyle.infoText}>{currency}{this.state.connectorInfo.rate}/{this.state.connectorInfo.rateType}</Text>
+          <Text style={ChargeStyle.infoTitle}>Rate</Text>
+        </View>
+        <View style={ChargeStyle.infoGroup}>
+          <Text
+            numberOfLines={1}
+            ellipsizeMode="tail"
+            style={ChargeStyle.infoText}>{this.state.connectorInfo.wattage}kW{/*this.state.connectorInfo.rateType*/}</Text>
+          <Text style={ChargeStyle.infoTitle}>Power</Text>
+        </View>
+        <View style={ChargeStyle.infoGroup}>
+          { this.state.isCharging
+          ? (this.state.isPending
+            ? (
+                <Text
+                  numberOfLines={1}
+                  ellipsizeMode="tail"
+                  style={[ChargeStyle.infoStatus, ChargeStyle.statusAuthenticated]}>
+                  Preparing
+                </Text>
+              )
+            : (
+                <Text
+                  numberOfLines={1}
+                  ellipsizeMode="tail"
+                  style={[ChargeStyle.infoStatus, ChargeStyle.statusAuthenticated]}>
+                  Charging
+                </Text>
+              )
+            )
+          : (this.state.connectorInfo.isCheckThrough
+            ? (
+                <Text
+                  numberOfLines={1}
+                  ellipsizeMode="tail"
+                  style={[ChargeStyle.infoStatus, ChargeStyle.statusAuthenticated]}>
+                  Authenticated
+                </Text>
+              )
+            : (
+                <Text
+                  numberOfLines={1}
+                  ellipsizeMode="tail"
+                  style={[ChargeStyle.infoStatus, ChargeStyle.statusError]}>
+                  Not Connected
+                </Text>
+              )
+            )
+          }
+          <Text style={ChargeStyle.infoTitle}>Status</Text>
+        </View>
+      </BadgeSelectItem>
+    );
+  }
+  //扫码之前-站点信息Section-end
+
+  //初始页面-扫码认证之前
+  StepRateView() {
+    return (
+      <View>
+        <View style={{minHeight: $vht(80)}}>
+          <Text style={styles.gstText}>All rates Include 8% GST</Text>
+          <Text style={styles.title}>AC Chargers ({this.state.stationInfo?.acConnector?.available ?? "0"} available)</Text>
+          { this.state.stationInfo.acRates?.length > 0
+            ? this.state.stationInfo.acRates.map((item, index) => {
+                return (
+                  <View key={index} style={ChargeStyle.stationInfoView}>
+                    <Image
+                      style={ChargeStyle.infoIcon}
+                      source={TypeImage.AC}/>
+                    <View style={ChargeStyle.infoGroup}>
+                      <Text style={ChargeStyle.infoText}>{item.type}</Text>
+                      <Text style={ChargeStyle.infoTitle}>Type</Text>
+                    </View>
+                    <View style={ChargeStyle.infoGroup}>
+                      <Text style={ChargeStyle.infoText}>{item.rates}</Text>
+                      <Text style={ChargeStyle.infoTitle}>Rate</Text>
+                    </View>
+                    <View style={ChargeStyle.infoGroup}>
+                      <Text style={ChargeStyle.infoText}>{item.power}</Text>
+                      <Text style={ChargeStyle.infoTitle}>Power</Text>
+                    </View>
+                    <View style={ChargeStyle.infoGroup}>
+                      { item?.connectorCount?.available > 0
+                        ? <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusAvailable]}>Available</Text>
+                        : <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusUnavailable]}>Unavailable</Text>
+                      }
+                      <Text style={ChargeStyle.infoTitle}>Status</Text>
+                    </View>
+                  </View>
+                );
+              })
+            : <Text style={ui.noData}>No Rates</Text>
+          }
+          <Text style={styles.title}>DC Chargers ({this.state.stationInfo?.dcConnector?.available ?? "0"} available)</Text>
+          { this.state.stationInfo.dcRates?.length > 0
+            ? this.state.stationInfo.dcRates.map((item, index) => {
+                return (
+                  <View key={index} style={ChargeStyle.stationInfoView}>
+                    <Image
+                      style={ChargeStyle.infoIcon}
+                      source={TypeImage.DC}/>
+                    <View style={ChargeStyle.infoGroup}>
+                      <Text style={ChargeStyle.infoText}>{item.type}</Text>
+                      <Text style={ChargeStyle.infoTitle}>Type</Text>
+                    </View>
+                    <View style={ChargeStyle.infoGroup}>
+                      <Text style={ChargeStyle.infoText}>{item.rates}</Text>
+                      <Text style={ChargeStyle.infoTitle}>Rate</Text>
+                    </View>
+                    <View style={ChargeStyle.infoGroup}>
+                      <Text style={ChargeStyle.infoText}>{item.power}</Text>
+                      <Text style={ChargeStyle.infoTitle}>Power</Text>
+                    </View>
+                    <View style={ChargeStyle.infoGroup}>
+                      { item?.connectorCount?.available > 0
+                        ? <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusAvailable]}>Available</Text>
+                        : <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusUnavailable]}>Unavailable</Text>
+                      }
+                      <Text style={ChargeStyle.infoTitle}>Status</Text>
+                    </View>
+                  </View>
+                );
+              })
+            : <Text style={ui.noData}>No Rates</Text>
+          }
+          { this.state.isPrivate &&
+            <View style={styles.privateView}> 
+              <Text style={styles.privateText}>NOTE: The charging stations are for private usage.</Text>
+            </View>
+          }
+        </View>
+        {/* <Payment refreshId={this.state.refreshId}/> */}
+        <View style={styles.buttonGroup}>
+          <Button
+            style={styles.buttonLeft}
+            text='Scan QR'
+            //disabled={this.state.available}
+            onClick={() => {
+              startPage(PageList.scanqr, {actionDetail: false, id: this.state.stationInfo.id});
+            }}/>
+          <Button
+            style={styles.buttonRight}
+            text='Enter Station ID'
+            //disabled={this.state.available}
+            onClick={() => {
+              this.setState({
+                showStationDialog: true
+              })
+            }}/>
+        </View>
+      </View>
+    );
+  }
+  //初始页面-扫码认证之前-end
+
+  //扫码认证之后-充电开始之前
+  StepStartView() {
+    return (
+      <>
+        <View style={{minHeight: $vht(80)}}>
+          <DashboardView isCharging={this.state.isCharging}/>
+          <Text style={styles.title}>Selected Charger</Text>
+          {this.StationInfo()}
+          {/* <View style={ui.center}>
+            <ImageBackground
+              style={styles.batteryBorder}
+              source={require('../../images/charge/ic-charge-circle.png')}>
+              <Text style={{
+                color: textPrimary,
+                fontSize: 16,
+                lineHeight: 22,
+                textAlign: 'center'
+              }}>
+                Press<Text style={{padding: 10, fontWeight: 'bold'}}> Start </Text>to begin Charging
+              </Text>
+            </ImageBackground>
+          </View> */}
+          <Text style={styles.title}>Select Payment Method</Text>
+          <PaymentList
+            payType={this.state.currentPayment}
+            payPerUse={this.state.curerntPerUser}
+            onMethodChange={(type) => this.onPaymentMethodChanged(type)}
+          />
+          {/* <Payment 
+            refreshId={this.state.refreshId}
+            payType={this.state.currentPaytype}
+            balance={this.state.curerntPerUser}
+            isPayPerUse={this.state.currentPayment == PAYTYPE.PAY_PER_USE}
+            onMethodChange={() => this.onMethodChange()}
+          /> */}
+        </View>
+        <Button
+          style={styles.buttonView}
+          text='START CHARGING'
+          elevation={1.5}
+          onClick={() => this.startCharge()}/>
+      </>
+    );
+  }
+  //扫码认证之后-充电开始之前-end
+
+  //正在充电页面
+  StepChargeView() {
+    return (
+      <>
+        <View style={{minHeight: $vht(80)}}>
+          <DashboardView isCharging={this.state.isCharging} connectorInfo={this.state.connectorInfo}/>
+          <Text style={styles.title}>Selected Charger</Text>
+          {this.StationInfo()}
+
+          {/* <BatteryView
+            soc={this.state.connectorInfo.batteryPercent}
+            isCharging={this.state.isCharging}
+            isPending={this.state.isPending}
+          /> */}
+          
+          <Text style={styles.title}>Select Payment Method</Text>
+          <PaymentList
+            isSelect={false}
+            payType={this.state.currentPayment}
+            payPerUse={this.state.curerntPerUser}
+            onMethodChange={(type) => this.onPaymentMethodChanged(type)}
+          />
+          {/* <Payment 
+            refreshId={this.state.refreshId}
+            payType={this.state.currentPaytype}
+            balance={this.state.curerntPerUser}
+          /> */}
+          { this.state.lastUpdated
+            ? <Text style={styles.updateTip}>{'Last updated at ' + this.state.lastUpdated + '\nPull down to refresh'}</Text>
+            : null
+          }
+        </View>
+        <Button
+          style={styles.buttonView}
+          disabled={this.state.isPending}
+          text={this.state.isCharging ? 'STOP CHARGING' : 'Complete'}
+          elevation={1.5}
+          onClick={() => {
+            if (this.state.isCharging) {
+              Dialog.showDialog({
+                title: 'Stop Charging',
+                message: 'Are you sure stop charging?',
+                callback: ok => {
+                  if (ok == Dialog.BUTTON_OK) {
+                    this.stopCharge();
+                  }
+                }
+              });
+            } else {
+              this.stopCharge();
+            }
+          }}/>
+      </>
+    );
+  }
+  //正在充电页面-end
+
+  //自动刷新
+  autoCheckIsCharge() {
+    if (this.canAutoRefresh) {
+      this.checkIsCharge();
+    }
+  }
+
+  //自动刷新状态
+  autoCheckChargeStatus() {
+    setTimeout(() => {
+      if (this.canAutoRefresh) {
+        this.checkChargeStatus();
+      }
+    }, 10000);
+  }
+
+  getConnectorInfo() {
+    console.log("getConnectorInfo", this.state.stationInfo.id);
+    apiCharge.checkIsCharging({sitePk: this.state.stationInfo.id}).then(res => {
+      this.setState({
+        connectorInfo: res.data
+      }, () => {
+        this.checkChargeStatus();
+      });
+    }).catch(err => {
+      
+    });
+  }
+
+  //获取充电数据(百分比)
+  checkIsCharge(showError) {
+    const params = {
+      sitePk: this.state.stationInfo.id
+    }
+    apiCharge.checkIsCharging(params).then(res => {
+      this.setState({
+        isStart: true,
+        isCharging: true,
+        isAuthentic: true,
+        connectorInfo: res.data,
+        lastUpdated: utils.getNowHHmm()
+      });
+      if (this.canAutoRefresh) {
+        setTimeout(() => {
+          this.autoCheckIsCharge();
+        }, 30000);
+      }
+      PagerUtil.onCharge();
+    }).catch(err => {
+      //TODO 模拟测试
+      this.setState({
+        isStart: false,
+        isCharging: false
+      });
+      setTimeout(() => {
+        this.autoCheckIsCharge();
+      }, 30000);
+      if (showError) {
+        this.setState({
+          errorCode: 'A4',
+          showErrorDialog: true,
+          errorMessage: 'Your vehicle doesn’t seem to be charging. Please check your vehicle.'
+        });
+      }
+    });
+  }
+
+  //获取充电桩对应接口的状态
+  checkChargeStatus() {
+    //TODO 模拟测试
+    /*this.setState({
+      isStart: true,
+      isCharging: true,
+      isAuthentic: true
+    });
+    return;*/
+    const params = {
+      connectorId: this.state.connectorInfo.connectorId,
+      chargeBoxId: this.state.connectorInfo.chargeBoxId,
+    }
+    if (!params.chargeBoxId || !params.connectorId) {
+      this.checkIsCharge();
+      return;
+    }
+    apiCharge.getCurrentStatus(params).then(res => {
+      if (res.data.status) {
+        switch (res.data.status) {
+          case 'Available': //可用的
+            this.state.connectorInfo.isCheckThrough = false;
+            this.setState({
+              isStart: false,
+              isPending: false,
+              isCharging: false,
+              connectorInfo: this.state.connectorInfo
+            });
+            break;
+          case 'Preparing': //已插入
+            this.state.connectorInfo.isCheckThrough = true;
+            this.setState({
+              isStart: false,
+              isPending: false,
+              isCharging: false,
+              available: true,
+              connectorInfo: this.state.connectorInfo
+            });
+            //this.checkIsCharge();
+            break;
+          case 'Charging': //正在充电
+            this.canAutoRefresh = true;
+            this.state.connectorInfo.isCheckThrough = true;
+            this.setState({
+              isPending: false,
+              connectorInfo: this.state.connectorInfo
+            });
+            this.checkIsCharge();
+            break;
+          case 'Initiating': //充电确认中
+            this.canAutoRefresh = true;
+            this.state.connectorInfo.isCheckThrough = true;
+            this.setState({
+              isStart: true,
+              isPending: true,
+              isCharging: true,
+              isAuthentic: true,
+              connectorInfo: this.state.connectorInfo
+            });
+            this.autoCheckChargeStatus();
+            break;
+          case 'SuspendedEVSE':
+            this.setState({
+              errorCode: 'A5',
+              showErrorDialog: true,
+              errorMessage: 'The charging station is unable to charge your vehicle.Please reauthenticate.'
+            });
+            break;
+          case 'SuspendedEV': //已连接上但未充电
+            this.checkIsCharge(true);
+            break;
+          case 'Reserved': //预定中
+            this.setState({
+              errorCode: 'A5',
+              showErrorDialog: true,
+              errorMessage: 'The charging station is reserved and unable to charge your vehicle.'
+            });
+            break;
+          case 'Finishing': //已完成
+            this.setState({
+              isStart: true,
+              isPending: false,
+              isCharging: false
+            });
+            break;
+          default:
+            this.setState({
+              isStart: false,
+              isPending: false,
+              isCharging: false
+            });
+            this.setState({
+              errorCode: 'A4',
+              showErrorDialog: true,
+              errorMessage: 'Your vehicle doesn’t seem to be charging. Please check your vehicle. (E0)'
+            });
+            break;
+        }
+      }
+    }).catch(err => {
+      toastShort(err)
+      this.setState({
+        errorCode: 'A9',
+        showErrorDialog: true,
+        errorMessage: 'There seems to be an authentication error! Please try again'
+      });
+    })
+  }
+
+  //开始充电api
+  startCharge() {
+    if (this.state.connectorInfo.isCheckThrough) {
+      Dialog.showProgressDialog();
+      const params = {
+        chargeBoxId: this.state.connectorInfo.chargeBoxId,
+        connectorId: this.state.connectorInfo.connectorId
+      }
+      apiCharge.startCharge(params).then(res => {
+        this.setState({
+          isStart: true,
+          isPending: true,
+          isCharging: true
+        });
+        this.canAutoRefresh = true;
+        this.autoCheckChargeStatus();
+        /*setTimeout(() => {
+          this.autoCheckIsCharge();
+        }, 30000);*/
+        Dialog.dismissLoading();
+      }).catch(err => {
+        //toastShort(err);
+        Dialog.dismissLoading();
+        this.setState({
+          errorCode: 'A4',
+          showErrorDialog: true,
+          errorMessage: ''+err
+        });
+      });
+    } else {
+      this.setState({
+        errorCode: 'A1',
+        showErrorDialog: true,
+        errorMessage: 'Your vehicle is not connected to the charging station. Please check the connector.'
+      });
+    }
+  }
+
+  //停止充电api
+  stopCharge() {
+    this.canAutoRefresh = false;
+    Dialog.showProgressDialog();
+    apiCharge.stopCharge().then(res => {
+      if (res.data.chargingPk) {
+        setTimeout(() => {
+          Dialog.dismissLoading();
+          this.setState({
+            isStart: false,
+            isPending: false,
+            isCharging: false
+          });
+          startPage(PageList.summary, { 
+            chargingPk: res.data.chargingPk,
+            id: this.state.stationInfo.id,
+            name: this.state.stationInfo.name,
+            address: this.state.stationInfo.address
+          });
+        }, 3000);
+      } else {
+        Dialog.dismissLoading();
+        toastShort('An error detected, please retry.')
+      }
+    }).catch(err => {
+      Dialog.dismissLoading();
+      toastShort(err);
+      this.setState({
+        isStart: false,
+        isPending: false,
+        isCharging: false
+      });
+      //模拟进入结算页
+      /*startPage(PageList.summary, {
+        chargingPk: 1,
+        id: this.state.stationInfo.id,
+        name: this.state.stationInfo.name,
+        address: this.state.stationInfo.address
+      });*/
+    });
+  }
+
+  closeError() {
+    this.setState({
+      showErrorDialog: false,
+      showStationDialog: false
+    });
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        { this.state.isAuthentic //是否扫码认证
+          ? this.state.isStart   //是否开始充电
+            ? this.StepChargeView()
+            : this.StepStartView()
+          : this.StepRateView()
+        }
+        <ErrorDialog
+          visible={this.state.showErrorDialog}
+          code={this.state.errorCode}
+          message={this.state.errorMessage}
+          onClose={() => {
+            this.closeError();
+          }}
+        />
+        <EnterStationDialog
+          visible={this.state.showStationDialog}
+          stationId={this.state.stationInfo.id}
+          onConfirm={() => this.enterStatioinId()}
+          onClose={() => this.closeError()}
+        />
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    paddingLeft: 16,
+    paddingRight: 16
+  },
+  title: {
+    color: '#000',
+    fontSize: 14,
+    fontWeight: 'bold',
+    paddingTop: 16,
+    paddingBottom: 16
+  },
+  gstText: {
+    color: '#EF3340',
+    fontSize: 16,
+    paddingTop: 16,
+    fontWeight: 'bold',
+    textAlign: 'center'
+  },
+  listView: {
+    padding: 8,
+  },
+  batteryBorder: {
+    margin: 30,
+    padding: 32,
+    width: circleSize,
+    height: circleSize,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  buttonView: {
+    marginTop: 16,
+    marginBottom: 32
+  },
+  buttonGroup: {
+    marginTop: 16,
+    marginBottom: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  buttonLeft: {
+    flex: 1,
+    elevation: 1.5,
+  },
+  buttonRight: {
+    flex: 1,
+    marginLeft: 16,
+    elevation: 1.5
+  },
+  inUse: {
+    color: '#fff',
+    fontSize: 12,
+    paddingTop: 4,
+    paddingLeft: 8,
+    paddingRight: 8,
+    paddingBottom: 4,
+    borderRadius: 4,
+    backgroundColor: '#FF7A00'
+  },
+  updateTip: {
+    color: '#aaa',
+    fontSize: 10,
+    textAlign: 'center',
+    paddingTop: 32,
+    paddingBottom: 16
+  },
+  privateView: {
+    height: $vht(25),
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  privateText: {
+    color: '#FA5759'
+  }
+})

+ 22 - 0
Strides-APP/app/pages/chargeV2/TabExplore.js

@@ -0,0 +1,22 @@
+/**
+ * 新版充电站Explore页面
+ * @邠心vbe on 2023/02/06
+ */
+import React, { Component } from 'react';
+import { View, Text } from 'react-native';
+
+export default class TabExplore extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+    };
+  }
+
+  render() {
+    return (
+      <View>
+        <Text> Comming soon... </Text>
+      </View>
+    );
+  }
+}

+ 94 - 0
Strides-APP/app/pages/chargeV2/TabInfos.js

@@ -0,0 +1,94 @@
+/**
+ * 新版充电站信息页面
+ * @邠心vbe on 2023/02/06
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+import PagerUtil from './PagerUtil';
+
+export default class TabInfos extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      stationInfo: {}
+    };
+  }
+
+  componentDidMount() {
+    PagerUtil.addOnRefresh(this);
+    this.onRefresh();
+  }
+
+  onRefresh() {
+    console.log("info刷新", this.props.route.name);
+    this.setState({
+      stationInfo: PagerUtil.getStationInfo()
+    });
+  }
+
+  getOperatingHours() {
+    if (this.state.stationInfo?.endlessService) {
+      return "24/7";
+    } else if (this.state.stationInfo?.operatingHours) {
+      return this.state.stationInfo?.operatingHours;
+    } else {
+      return 'To be updated';
+    }
+  }
+
+  getParkingFee() {
+    if (this.state.stationInfo?.parkingFeeFree) {
+      return "Free";
+    } else if (this.state.stationInfo?.parkingFee) {
+      return this.state.stationInfo.parkingFee;
+    } else {
+      return 'To be updated';
+    }
+  }
+  
+  render() {
+    return (
+      <View style={$padding(0, 16)}>
+        <Text style={styles.title}>Site Name</Text>
+        <View style={styles.infoView}>
+          <Text style={styles.infoText}>{this.state.stationInfo?.name}</Text>
+        </View>
+        <Text style={styles.title}>Address</Text>
+        <View style={styles.infoView}>
+          <Text style={styles.infoText}>{this.state.stationInfo?.address}</Text>
+        </View>
+        <Text style={styles.title}>Parking Fees</Text>
+        <View style={styles.infoView}>
+          <Text style={styles.infoText}>{this.getParkingFee()}</Text>
+        </View>
+        <Text style={styles.title}>Operating Hours</Text>
+        <View style={styles.infoView}>
+          <Text style={styles.infoText}>{this.getOperatingHours()}</Text>
+        </View>
+        <Text style={styles.title}>Additional Information</Text>
+        <View style={styles.infoView}>
+          <Text style={styles.infoText}>{this.state.stationInfo?.additionalNotes}</Text>
+        </View>
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  title: {
+    color: '#000',
+    fontSize: 14,
+    fontWeight: 'bold',
+    ...$padding(16, 0, 8),
+    borderBottomColor: '#eee',
+    borderBottomWidth: 1
+  },
+  infoView: {
+    paddingTop: 8,
+    paddingBottom: 16
+  },
+  infoText: {
+    color: '#444',
+    fontSize: 14
+  }
+})

+ 530 - 0
Strides-APP/app/pages/chargeV2/TabReserve.js

@@ -0,0 +1,530 @@
+/**
+ * 新版充电预定页面
+ * @邠心vbe on 2023/02/06
+ */
+ import React, { Component } from 'react'
+ import { Image, StyleSheet, Text, View } from 'react-native'
+ import Button from '../../components/Button';
+ import { ChargeStyle, EnterStationDialog, TypeImage } from './Charging';
+ import apiCharge from '../../api/apiCharge';
+ import Dialog from '../../components/Dialog';
+ import { PageList } from '../Router';
+ import { CancelReserveDialog } from './InfoDialog';
+import PagerUtil from './PagerUtil';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
+
+export default class TabReserve extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      total: 0,
+      leftId: 0,
+      refreshId: 0,
+      checkIndex: -1,
+      available: false,
+      showReserve: false,
+      stationInfo: {},
+      checkConnector: {},
+      userReserve: {},
+      timeLeft: '',
+      showCancelDialog: false,
+      showStationDialog: false
+    };
+  }
+
+  componentDidMount() {
+    PagerUtil.addOnRefresh(this);
+    this.onRefresh();
+  }
+
+  onRefresh() {
+    console.log("Reserve刷新", this.props.route.name);
+    this.setState({
+      stationInfo: PagerUtil.getStationInfo()
+    }, () => this.init());
+  }
+
+  init() {
+    this.stopCountdown(true);
+    if (this.state.stationInfo.rateList && this.state.stationInfo.rateList.length > 0) {
+      for (var i = 0; i < this.state.stationInfo.rateList.length; i++) {
+        const item = this.state.stationInfo.rateList[i]
+        if (item.available) {
+          this.setState({
+            checkIndex: i,
+            checkConnector: item,
+          })
+          break;
+        }
+      }
+      this.setState({
+        total: this.state.stationInfo.rateList.length,
+        showReserve: this.state.stationInfo.enableReservation ? true : false
+      })
+      this.getReserve();
+      //this.refreshAvailable();
+    } else {
+      this.setState({
+        showReserve: false
+      })
+    }
+  }
+
+  //刷新可用充电接口
+  refreshAvailable() {
+    const info = this.state.stationInfo
+    const all = info?.allConnector;
+    /*if (info.siteType == 'Private') {
+      this.setState({
+        isPrivate: true
+      })
+    }*/
+    if (all) {
+      this.setState({
+        available: !all.available > 0
+      });
+    }
+  }
+
+  checkChange(index) {
+    if (this.state.checkIndex !== index) {
+      this.setState({
+        checkIndex: index,
+        checkConnector: this.state.stationInfo.rateList[index],
+      })
+    }
+  }
+
+  getAvailable(type) {
+    let count = type;
+    if (typeof type === 'string') {
+      count = type == "AC" ? this.state.stationInfo.acConnector :  this.state.stationInfo.dcConnector;
+    }
+    if (count) {
+      return count.available + '/' + count.all;
+    } else {
+      return '0/0';
+    }
+  }
+
+  getReserve() {
+    apiCharge.getUserReserve(this.state.stationInfo.id).then(res => {
+      if (res.data.reservePk && res.data.reserveEndTimeTimestamp > 0) {
+        this.setState({
+          userReserve: res.data
+        }, () => this.startCountdown());
+      } else {
+        this.stopCountdown();
+      }
+    }).catch(err => {
+      this.stopCountdown();
+    });
+  }
+
+  onReserve() {
+    if (this.state.checkConnector?.chargeTypePk) {
+      Dialog.showProgressDialog();
+      apiCharge.reserveCharge({
+        sitePk: this.state.stationInfo.id,
+        chargeTypePk: this.state.checkConnector.chargeTypePk
+      }).then(res => {
+        Dialog.dismissLoading();
+        toastShort('Reserved successfully!');
+        PagerUtil.setBackRefreshing();
+        this.getReserve();
+      }).catch(err => {
+        Dialog.dismissLoading();
+        toastShort(err)
+      });
+    } else {
+      toastShort("Please select a connnector")
+    }
+  }
+
+  onCancel() {
+    if (this.state.userReserve.reservePk) {
+      Dialog.showProgressDialog();
+      apiCharge.cancelReserve(this.state.userReserve.reservePk).then(res => {
+        Dialog.dismissLoading();
+        PagerUtil.setBackRefreshing();
+        toastShort('Cancel successfully!');
+        this.getReserve();
+      }).catch(err => {
+        Dialog.dismissLoading();
+        toastShort(err)
+      });
+    }
+  }
+
+  cancelReserve() {
+    // this.setState({
+    //   showCancelDialog: true
+    // });
+    Dialog.showDialog({
+      title: 'Cancle Reservation',
+      message: 'Are you sure you want to cancle reservation?',
+      ok: 'YES',
+      cancel: 'NO',
+      callback: (btn => {
+        if (btn == "ok") {
+          this.onCancel();
+        }
+      })
+    })
+  }
+
+  startCountdown() {
+    if (this.state.userReserve.reserveEndTimeTimestamp > 0) {
+      PagerUtil.onReserve();
+      const leftId = this.state.leftId;
+      this.countdown(leftId);
+    } else {
+      this.stopCountdown(false, true);
+    }
+  }
+
+  countdown(leftId) {
+    if (leftId != this.state.leftId) {
+      console.log(leftId, this.state.leftId);
+      return;
+    }
+    const now = new Date().getTime();
+    let left = this.state.userReserve.reserveEndTimeTimestamp - now;
+    let s = 0, m = 0, h = 0;
+    if (left > 1000) {
+      s = left / 1000;
+      if (s > 60) {
+        m = s / 60;
+        s = s % 60;
+        if (m > 60) {
+          h = m / 60;
+          m = m % 60;
+        }
+      }
+    } else {
+      this.stopCountdown(false, true)
+    }
+    this.setState({
+      timeLeft: this.formatNumber(h) + ' : ' + this.formatNumber(m) + ' : ' + this.formatNumber(s)
+    });
+    setTimeout(() => {
+      this.countdown(leftId);
+    }, 1000);
+  }
+
+  formatNumber(ins) {
+    const num = parseInt(ins)
+    if (num > 0) {
+      if (num < 10) {
+        return '0' + num;
+      } else {
+        return num;
+      }
+    } else {
+      return '00';
+    }
+  }
+
+  stopCountdown(just, refresh) {
+    if (just) {
+      this.setState({
+        leftId: this.state.leftId + 1
+      });
+    } else {
+      this.setState({
+        leftId: this.state.leftId + 1,
+        userReserve: {}
+      });
+    }
+    if (refresh) {
+      PagerUtil.setBackRefreshing();
+    }
+  }
+
+  enterStatioinId() {
+    PagerUtil.onEnterStation();
+  }
+
+  render() {
+    return (
+      <View
+        style={{
+          paddingLeft: 16,
+          paddingRight: 16,
+          ...this.props.style
+        }}>
+        { this.state.showReserve
+          ? (
+            this.state.userReserve.reservePk
+              ? this.countdownView()
+              : this.reserveView()
+            )
+          : <View style={[{height: $vh(50)}, ui.flexvc]}>
+              <Text style={{color: textPrimary, fontSize: 14}}>Reservation is not available for this site.</Text>
+            </View>
+        }
+        <CancelReserveDialog
+          visible={this.state.showCancelDialog}
+          onClose={confirm => {
+            this.setState({
+              showCancelDialog: false
+            });
+            if (confirm) {
+              this.onCancel();
+            }
+          }}/>
+        <EnterStationDialog
+          visible={this.state.showStationDialog}
+          stationId={this.state.stationInfo.id}
+          onConfirm={() => this.enterStatioinId()}
+          onClose={() => {
+            this.setState({
+              showStationDialog: false
+            });
+          }}
+        />
+      </View>
+    );
+  }
+
+  //预定页面
+  reserveView() {
+    return (
+      <>
+      <View style={{minHeight: $vht(75)}}>
+        <Text style={styles.title}>Choose Connector</Text>
+        { this.state.total > 0
+          ? this.state.stationInfo.rateList.map((item, index) => {
+            const _type = item.type?.indexOf('AC') >= 0 ? 'AC' : 'DC';
+            return (
+              <BadgeSelectItem
+                key={index}
+                style={ChargeStyle.stationInfoView}
+                onPress={() => {
+                    if (item.available) {
+                      this.checkChange(index)
+                    }
+                  }
+                }
+                checked={index == this.state.checkIndex}>
+                {/* <SelectableIcon selected={index == this.state.checkIndex}>
+                  
+                </SelectableIcon> */}
+                <Image
+                  style={ChargeStyle.infoIcon}
+                  source={_type == "AC" ? TypeImage.AC : TypeImage.DC}/>
+                <View style={ChargeStyle.infoGroup}>
+                  <Text style={ChargeStyle.infoText}>{item.type}</Text>
+                  <Text style={ChargeStyle.infoTitle}>Type</Text>
+                </View>
+                <View style={ChargeStyle.infoGroup}>
+                  <Text style={ChargeStyle.infoText}>{item.power}</Text>
+                  <Text style={ChargeStyle.infoTitle}>Power</Text>
+                </View>
+                <View style={ChargeStyle.infoGroup}>
+                  <Text style={ChargeStyle.infoText}>{this.getAvailable(_type)}</Text>
+                  <Text style={ChargeStyle.infoTitle}>Available/Total</Text>
+                </View>
+                <View style={ChargeStyle.infoGroup}>
+                  { item?.connectorCount?.available > 0
+                    ? <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusAvailable]}>Available</Text>
+                    : <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusUnavailable]}>Unavailable</Text>
+                  }
+                  <Text style={ChargeStyle.infoTitle}>Status</Text>
+                </View>
+                {/* index == this.state.checkIndex
+                  ? <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusSelected]}>Selected</Text>
+                  : (item.available
+                    ? <TextRadius style={[ChargeStyle.infoStatus, ChargeStyle.statusAvailable]}>Available</TextRadius>
+                    : <TextRadius style={[ChargeStyle.infoStatus, ChargeStyle.statusUnavailable]}>Unavailable</TextRadius>
+                  )
+                */}
+              </BadgeSelectItem>
+            )
+          }) : null
+        }
+        { this.state.checkConnector.available
+          ? <>
+              <Text style={styles.title}>Choose Rate</Text>
+              <BadgeSelectItem
+                style={ChargeStyle.stationInfoView}
+                checked={true}>
+                <Image 
+                  style={ChargeStyle.infoIcon}
+                  source={require('../../images/charge/ic-type-rate.png')}/>
+                <Text style={ChargeStyle.rateText}>Rate</Text>
+                <Text style={[ChargeStyle.ratePrice]}>{this.state.checkConnector.rates}</Text>
+                <Text></Text>
+                {/* <Text style={[ChargeStyle.infoStatus, ChargeStyle.statusSelected]}>Selected</Text> */}
+              </BadgeSelectItem>
+            </>
+          : <View style={{height: 60}}></View>
+        }
+
+        {/* <Text style={styles.title}>Choose Payment Method</Text>
+        <Payment refreshId={this.state.refreshId}/> */}
+      </View>
+      <Button
+        style={styles.buttonView}
+        elevation={1.5}
+        text='Reserve'
+        disabled={this.state.available}
+        onClick={() => this.onReserve()}
+      />
+      </>
+    )
+  }
+
+  //倒计时页面
+  countdownView() {
+    let info = this.state.userReserve.siteRate
+    return (
+      <>
+        <View style={{minHeight: $vht(80)}}>
+          <Text style={styles.title}>Your Selection</Text>
+          { info &&
+            <View>
+              <BadgeSelectItem
+                style={ChargeStyle.stationInfoView}
+                checked={true}>
+                <Image
+                  style={ChargeStyle.infoIcon}
+                  source={info.type?.indexOf('AC') >= 0 ? TypeImage.AC : TypeImage.DC}/>
+                <View style={ChargeStyle.infoGroup}>
+                  <Text style={ChargeStyle.infoText}>{info.type}</Text>
+                  <Text style={ChargeStyle.infoTitle}>Type</Text>
+                </View>
+                <View style={ChargeStyle.infoGroup}>
+                  <Text style={ChargeStyle.infoText}>{info.power}</Text>
+                  <Text style={ChargeStyle.infoTitle}>Power</Text>
+                </View>
+                <View style={ChargeStyle.infoGroup}>
+                  <Text style={ChargeStyle.infoBoldNumber}>{this.getAvailable(info.connectorCount)}</Text>
+                  <Text style={ChargeStyle.infoTitle}>Available/Total</Text>
+                </View>
+                <Text></Text>
+                {/* <SelectableIcon selected={true}/> */}
+              </BadgeSelectItem>
+              <BadgeSelectItem
+                checked={true}
+                style={ChargeStyle.stationInfoView}>
+                <Image 
+                  style={ChargeStyle.infoIcon}
+                  source={require('../../images/charge/ic-type-rate.png')}/>
+                <Text style={ChargeStyle.rateText}>Rate</Text>
+                <Text style={[ChargeStyle.ratePrice]}>{info.rates}</Text>
+                <Text></Text>
+                {/* <SelectableIcon selected={true}/> */}
+              </BadgeSelectItem>
+            </View>
+          }
+          <Text style={styles.timeleftText}>Reservation time left</Text>
+          <View style={styles.timeleftView}>
+            <Text style={styles.timeleft}>{this.state.timeLeft}</Text>
+          </View>
+          <View style={styles.cancelView}>
+            <Button
+              text='Cancel Reservation'
+              textColor={textButton}
+              style={styles.cancelButton}
+              viewStyle={styles.cancelButtonView}
+              onClick={() => this.cancelReserve()}
+            />
+          </View>
+
+          {/* <Text style={styles.title}>Choose Payment Method</Text>
+          <Payment refreshId={this.state.refreshId}/> */}
+        </View>
+        <View style={styles.buttonGroup}>
+          <Button
+            style={styles.buttonLeft}
+            text='Scan QR'
+            disabled={this.state.available}
+            onClick={() => {
+              startPage(PageList.scanqr, {actionDetail: false, id: this.state.stationInfo.id});
+            }}/>
+          <Button
+            style={styles.buttonRight}
+            text='Enter Station ID'
+            disabled={this.state.available}
+            onClick={() => {
+              this.setState({
+                showStationDialog: true
+              });
+              //startPage(PageList.summary)
+            }
+          }/>
+        </View>
+      </> 
+    )
+  }
+}
+
+const styles = StyleSheet.create({
+  title: {
+    color: '#000',
+    fontSize: 15,
+    fontWeight: 'bold',
+    paddingTop: 16,
+    paddingBottom: 16
+  },
+  listView: {
+    padding: 8,
+    borderRadius: 8,
+    backgroundColor: '#F5F5F5'
+  },
+  buttonView: {
+    marginTop: 32,
+    marginBottom: 16
+  },
+  timeleftText: {
+    color: '#000',
+    fontSize: 16,
+    fontWeight: 'bold',
+    paddingTop: 16,
+    paddingBottom: 8,
+    textAlign: 'center'
+  },
+  timeleftView: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    marginBottom: 16
+  },
+  timeleft: {
+    color: '#FF2E00',
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  cancelView: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    paddingTop: 8,
+    paddingBottom: 8
+  },
+  cancelButton: {
+    borderRadius: 10,
+    backgroundColor: '#ED4A4A'
+  },
+  cancelButtonView: {
+    height: 48,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  buttonGroup: {
+    marginTop: 16,
+    marginBottom: 24,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  buttonLeft: {
+    flex: 1,
+    elevation: 1.5,
+  },
+  buttonRight: {
+    flex: 1,
+    marginLeft: 16,
+    elevation: 1.5
+  },
+})

+ 205 - 123
Strides-APP/app/pages/home/Drawer.js

@@ -3,7 +3,7 @@
  * @邠心vbe on 2021/03/23
  */
 import React, { Component } from 'react';
-import {View, Text, StyleSheet, Image, Pressable, BackHandler, Linking} from 'react-native';
+import {View, Text, StyleSheet, Image, Pressable, BackHandler, Linking, Touchable} from 'react-native';
 import { createDrawerNavigator, DrawerContentScrollView } from '@react-navigation/drawer';
 import { Styles } from '../../components/Toolbar';
 import app from '../../../app.json';
@@ -16,11 +16,10 @@ import Button from '../../components/Button';
 import { AutoLogin, RegisterToken } from '../sign/Login';
 import apiCharge from '../../api/apiCharge';
 import { toTopupPage } from '../wallet/Payment';
+import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
 
 const Drawer = createDrawerNavigator();
 
-const drawerBackgroundColor = '#FFFCF8';
-
 const DEBUG = app.debug && !app.product;
 
 export default class Home extends Component {
@@ -104,7 +103,7 @@ export default class Home extends Component {
         }
         drawerStyle={{
           width: $vw(75) > 320 ? 320 : $vw(75),
-          backgroundColor: drawerBackgroundColor
+          backgroundColor: colorLight
         }}>
         <Drawer.Screen name="maps" component={Maps} />
       </Drawer.Navigator>
@@ -128,7 +127,8 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
     apiCharge.getUserCharging().then(res => {
       Dialog.dismissLoading();
       if (res.data.sitePk) {
-        startPage(PageList.chargeDetail, { stationInfo: {id: res.data.sitePk}, action: 'view'});
+        startPage(PageList.chargeDetailPage, {stationInfo: {id: res.data.sitePk}, action: 'view', from: PageList.home});
+        //startPage(PageList.chargeDetail, { stationInfo: {id: res.data.sitePk}, action: 'view'});
       } else if (res.msg) {
         toastShort(res.msg);
       } else {
@@ -157,37 +157,51 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
 
   return (
     <View style={styles.drawerView}>
-      { isLogin
-        ? <View style={styles.loginView}>
-            <Text
-              style={styles.nickname}
-              ellipsizeMode='tail'
-              numberOfLines={1}>
-              { userInfo.nickName 
+      <View style={styles.loginView}>
+        { (isLogin && userInfo.photoUrl)
+        ? <TouchableWithoutFeedback onPress={() => startPage(PageList.profile)}>
+            <Image
+              style={styles.avatar}
+              source={{uri: host + userInfo.photoUrl}}/>
+          </TouchableWithoutFeedback>
+        : <TouchableWithoutFeedback onPress={() => startPage(PageList.login)}>
+            <Image
+              style={styles.avatar}
+              source={require('../../images/user/ic-avatar-default.png')}/>
+          </TouchableWithoutFeedback>
+        }
+        <Pressable
+          style={styles.nickView}
+          android_ripple={ripple}
+          onPress={() => startPage(isLogin ? PageList.profile : PageList.login)}>
+          <Text
+            style={styles.nickname}
+            ellipsizeMode='tail'
+            numberOfLines={1}>
+            { isLogin
+              ? userInfo.nickName 
                 ? userInfo.nickName
                 : 'Logging...'
-              }
-            </Text>
-          </View> 
-        : <View style={styles.guessView}>
-            <Image
-              style={Styles.logo}
-              source={require('../../images/app-logo.png')}/>
-          </View>
-      }
-      
-      { isLogin
+              : "Sign In"
+            }
+          </Text>
+          <FontAwesome
+            size={24}
+            color='#999'
+            name='angle-right'/>
+        </Pressable>
+      </View> 
+      <View style={styles.divideLogin}></View>
+      {/* isLogin
         ? <Button
             style={styles.itemButton}
             viewStyle={styles.itemView}
             onClick={() => {
               startPage(PageList.profile)
             }}>
-            <MaterialCommunityIcons
+            <Image
               style={styles.icon}
-              name="account-outline"
-              color="#333"
-              size={26}/>
+              source={require('../../images/icon/draw-user.png')}/>
             <Text style={styles.label}>Profile Settings</Text>
           </Button>
         : <Button
@@ -196,43 +210,39 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
             onClick={() => {
               startPage(PageList.login); 
             }}>
-            <MaterialCommunityIcons
-              style={styles.icon2}
-              name="login-variant"
-              color="#333"
-              size={24}/>
+            <Image
+              style={styles.icon}
+              source={require('../../images/icon/draw-user.png')}/>
             <Text style={styles.label}>Sign In</Text>
           </Button>
-      }
+      */}
 
-      <Button
+      {/* <Button
         style={styles.itemButton}
         viewStyle={styles.itemView}
         onClick={() => {
           navigation.toggleDrawer();
         }}>
-        <MaterialCommunityIcons
+        <Image
           style={styles.icon}
-          name="home-outline"
-          color="#333"
-          size={26}/>
+          source={require('../../images/icon/draw-home.png')}/>
         <Text style={styles.label}>Home</Text>
-      </Button>
+      </Button> */}
 
       { isLogin
         ? <Button
             style={styles.itemButton}
             viewStyle={styles.itemView}
             onClick={() => getCharging()}>
-            {/* <Image
-              style={styles.icon}
-              source={require('../../images/icon/draw-charge.png')}/> */}
             <MaterialCommunityIcons
               style={styles.icon}
               name="car-electric-outline"
               color="#333"
               size={26}
             />
+            {/* <Image
+              style={styles.icon}
+              source={require('../../images/icon/draw-charge.png')}/> */}
             <Text style={styles.label}>Charging</Text>
           </Button>
         : <View
@@ -249,7 +259,28 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
             <Text style={styles.disable}>Charging</Text>
           </View>
       }
-      { isLogin &&
+      {/* isLogin
+        ? <Button
+            style={styles.itemButton}
+            viewStyle={styles.itemView}
+            onClick={() => {
+              startPage(PageList.wallet);
+            }}>
+            <Image
+              style={styles.icon}
+              source={require('../../images/icon/draw-transaction.png')}/>
+            <Text style={styles.label}>Transactions</Text>
+          </Button>
+        : <View
+            style={styles.disableItem}>
+            <Image
+              style={styles.icon}
+              source={require('../../images/icon/draw-transaction-no.png')}/>
+            <Text style={styles.disable}>Transactions</Text>
+          </View>
+      */}
+      {
+        isLogin && <>
         <Button
           style={styles.itemButton}
           viewStyle={styles.itemView}
@@ -261,14 +292,18 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
             source={require('../../images/icon/draw-transaction.png')}/>
           <Text style={styles.label}>Transactions</Text>
         </Button>
-      }
-      {/* <View
-          style={styles.disableItem}>
-          <Image
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => toTopupPage()}>
+          <MaterialCommunityIcons
             style={styles.icon}
-            source={require('../../images/icon/draw-transaction-no.png')}/>
-          <Text style={styles.disable}>Transactions</Text>
-        </View> */
+            size={26}
+            name={"wallet-outline"}
+            color={colorDark}/>
+          <Text style={styles.label}>Wallet</Text>
+          <Text style={styles.balanceText2}>{currency}{userInfo.credit}</Text>
+        </Button></>
       }
 
       <Button
@@ -285,57 +320,6 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
         <Text style={styles.label}>Feedback</Text>
       </Button>
 
-      <Button
-        style={styles.itemButton}
-        viewStyle={styles.itemView}
-        onClick={() => {
-          startPage(PageList.privacy);
-        }}>
-        <Image
-          style={styles.icon}
-          source={require('../../images/icon/draw-privacy.png')}/>
-        <Text style={styles.label}>Privacy Policy</Text>
-      </Button>
-      <Button
-        style={styles.itemButton}
-        viewStyle={styles.itemView}
-        onClick={() => {
-          startPage(PageList.condition);
-        }}>
-        <MaterialCommunityIcons
-          style={styles.icon}
-          name="file-eye-outline"
-          color="#222"
-          size={26}/>
-        <Text style={styles.label}>Terms of Use</Text>
-      </Button>
-
-      { DEBUG &&
-      <>
-        <Button
-          style={styles.itemButton}
-          viewStyle={styles.itemView}
-          onClick={() => {
-            startPage(PageList.notify);
-          }}>
-          <Image
-            style={styles.icon2}
-            source={require('../../images/icon/draw-terms.png')}/>
-          <Text style={styles.label}>Notification</Text>
-        </Button>
-        <Button
-          style={styles.itemButton}
-          viewStyle={styles.itemView}
-          onClick={() => {
-            startPage(isIOS ? PageList.notify : PageList.mapTest);
-          }}>
-          <Image
-            style={styles.icon2}
-            source={require('../../images/icon/draw-terms.png')}/>
-          <Text style={styles.label}>Maps Test</Text>
-        </Button>
-      </>}
-
       {/* <Button
         style={styles.itemButton}
         viewStyle={styles.itemView}
@@ -345,7 +329,7 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
         <Feather
           style={styles.icon2}
           name="download"
-          color="#333"
+          color={colorDark}
           size={24}
         />
         <Text style={styles.label}>FAQs</Text>
@@ -379,9 +363,69 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
         <Text style={styles.label}>About Us</Text>
       </Button>
 
-      <View style={styles.divided}></View>
+      {/* <View style={styles.divided}></View> */}
+      { DEBUG &&
+      <>
+        <View style={styles.divideLogin}></View>
+        <Text style={{color: "#ccc", paddingLeft: 16, paddingBottom: 8, fontSize: 12}}>Debug Mode Options Only</Text>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => {
+            startPage(PageList.privacy);
+          }}>
+          <MaterialCommunityIcons
+            style={styles.icon}
+            name="file-eye-outline"
+            color="#222"
+            size={26}/>
+          <Text style={styles.label}>Privacy Policy</Text>
+        </Button>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => {
+            startPage(PageList.condition);
+          }}>
+          <MaterialCommunityIcons
+            style={styles.icon}
+            name="file-eye-outline"
+            color="#222"
+            size={26}/>
+          <Text style={styles.label}>Terms of Use</Text>
+        </Button>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => {
+            startPage(PageList.notify);
+          }}>
+          {/* <Image
+            style={styles.icon2}
+            source={require('../../images/icon/draw-terms.png')}/> */}
+          <MaterialIcons
+            style={styles.icon}
+            name="notifications-none"
+            color="#222"
+            size={26}/>
+          <Text style={styles.label}>Notification Test</Text>
+        </Button>
+        <Button
+          style={styles.itemButton}
+          viewStyle={styles.itemView}
+          onClick={() => {
+            startPage(isIOS ? PageList.notify : PageList.mapTest);
+          }}>
+          <MaterialCommunityIcons
+            style={styles.icon}
+            name="map-legend"
+            color="#222"
+            size={26}/>
+          <Text style={styles.label}>Maps Test</Text>
+        </Button>
+      </>}
 
-      { isLogin
+      {/* isLogin
         ? <View style={styles.walletView}>
             <Pressable
               style={styles.balanceView}
@@ -402,22 +446,27 @@ const DrawerContent = ({isLogin, userInfo, onLogout, navigation}) => {
                 style={styles.icon}
                 source={require('../../images/icon/draw-gift.png')}/>
               <Text style={styles.referText}>Refer your friends to get $5 credit!</Text>
-            </Pressable> */}
+            </Pressable> *}
           </View>
-        : <View style={ui.flex1}></View>
-      }
+        : <View style={ui.flex1}></View>s
+        */}
 
       <View style={styles.bottomView}>
-        <Text style={{ color: '#333', fontSize: 10 }}>{app.displayName + '  ' + app.versionName}</Text>
+        <Image
+          style={Styles.logo}
+          resizeMode='contain'
+          source={require('../../images/app-logo.png')}
+        />
+        <Text style={styles.versionText}>{app.displayName + '  ' + app.versionName}</Text>
       </View>
-      { isLogin &&
+      {/* isLogin &&
         <Button
           style={styles.logoutView}
           text='Logout'
-          textColor='#fff'
+          textColor={textButton}
           borderRadius={0}
           onClick={() => logout()}/>
-      }
+      */}
     </View>
   );
 }
@@ -430,28 +479,50 @@ const styles = StyleSheet.create({
     height: statusHeight,
     position: 'absolute',
     fontSize: 10,
-    backgroundColor: drawerBackgroundColor
+    backgroundColor: pageBackground
   },
   drawerView: {
     paddingTop: 16,
-    minHeight: $vhs(100)
+    paddingBottom: 8,
+    minHeight: $vhs(101)
   },
   guessView: {
     padding: 16
   },
   loginView: {
-    padding: 16
+    paddingTop: 16,
+    paddingBottom: 8
+  },
+  avatar: {
+    width: 66,
+    height: 66,
+    marginLeft: 24,
+    borderWidth: 2,
+    borderRadius: 80,
+    borderColor: colorLight,
+  },
+  nickView: {
+    marginTop: 4,
+    flexDirection: 'row',
+    ...$padding(12, 16)
   },
   nickname: {
-    color: '#333',
-    fontSize:21,
+    flex: 1,
+    color: textPrimary,
+    fontSize: 20,
     fontWeight: 'bold',
-    paddingTop: 8,
-    paddingBottom: 16
+    paddingLeft: 16,
+  },
+  divideLogin: {
+    height: 1,
+    marginTop: 4,
+    marginRight: 32,
+    marginBottom: 12,
+    backgroundColor: '#E5E5E5'
   },
   itemButton: {
     borderRadius: 0,
-    backgroundColor: drawerBackgroundColor
+    backgroundColor: colorLight
   },
   itemView: {
     flex: 1,
@@ -480,7 +551,8 @@ const styles = StyleSheet.create({
     marginRight: 17
   },
   label: {
-    color: '#333',
+    flex: 1,
+    color: textPrimary,
     fontSize: 16.5
   },
   disable: {
@@ -508,23 +580,33 @@ const styles = StyleSheet.create({
     backgroundColor: 'rgba(0, 20, 137, 0.2)'
   },
   balanceText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 22,
     fontWeight: 'bold'
   },
   referText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 13
   },
   bottomView: {
     flex: 1,
     paddingTop: 48,
-    paddingBottom: 8,
+    paddingBottom: 0,
     alignItems: 'center',
     justifyContent: 'flex-end'
   },
+  versionText: {
+    color: textPrimary,
+    fontSize: 10,
+    paddingTop: 16
+  },
   logoutView: {
     backgroundColor: colorPrimary
+  },
+  balanceText2: {
+    color: textCancel,
+    fontSize: 14,
+    marginRight: 32
   }
 });

+ 7 - 6
Strides-APP/app/pages/home/maps/BottomSiteInfo.js

@@ -8,7 +8,7 @@ import { ElevationObject } from '../../../components/Button';
 import utils from '../../../utils/utils';
 import { ChargeStyle } from '../../charge/Charging';
 import { PageList } from '../../Router';
-import { ConnectType } from '../Search';
+import { ConnectType } from '../../search/Search';
  
  
 export default TopInfo = ({stationInfo = {}}) => {
@@ -51,7 +51,8 @@ export default TopInfo = ({stationInfo = {}}) => {
         <View style={ui.flexc}>
           <Pressable
             style={styles.stationInfo}
-            onPress={() => startPage(PageList.chargeDetail, {stationInfo: stationInfo, action: 'view'})}>
+            //onPress={() => startPage(PageList.chargeDetail, {stationInfo: stationInfo, action: 'view'})}}
+            onPress={() => startPage(PageList.chargeDetailPage, {stationInfo: stationInfo, action: 'view', from: PageList.home})}>
             <Text
               ellipsizeMode='tail'
               numberOfLines={1}
@@ -100,7 +101,7 @@ export default TopInfo = ({stationInfo = {}}) => {
         <View style={styles.divideLine}></View>
         <Pressable
           style={styles.infoDetailsView}
-          onPress={() => startPage(PageList.chargeDetail, {stationInfo: stationInfo, action: 'view'})}>
+          onPress={() => startPage(PageList.chargeDetailPage, {stationInfo: stationInfo, action: 'view', from: PageList.home})}>
           <View style={ui.flex1}>
             <Text style={styles.infoTitle}>Operating Hours</Text>
             <View style={styles.infoView}>
@@ -138,7 +139,7 @@ const styles = StyleSheet.create({
     borderRadius: 6,
     ...$padding(12, 9),
     position: 'absolute',
-    backgroundColor: 'white',
+    backgroundColor: colorLight,
     ...ElevationObject(1.5)
   },
   stationInfo: {
@@ -184,7 +185,7 @@ const styles = StyleSheet.create({
     height: 23,
   },
   availableText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 13,
     textAlign: 'center'
   },
@@ -197,7 +198,7 @@ const styles = StyleSheet.create({
     justifyContent: 'space-between'
   },
   distanceText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 12,
   },
   connectView: {

+ 25 - 20
Strides-APP/app/pages/home/maps/Filter.js

@@ -3,8 +3,9 @@
  * @邠心vbe on 2021/03/24
  */
 import React from 'react'
-import { Image, Pressable, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
-import Button from '../../../components/Button';
+import { Image, Pressable, StyleSheet, Text, TouchableOpacity, TouchableOpacityBase, View } from 'react-native'
+import BadgeSelectItem from '../../../components/BadgeSelectItem';
+import Button, { ElevationObject } from '../../../components/Button';
 import { TypeImageList } from '../../charge/Charging';
 
 class Filter extends React.Component {
@@ -125,21 +126,17 @@ class Filter extends React.Component {
         <View style={styles.connectorView}>
           { this.state.connectorType.map((item, index) => {
               return (
-                <TouchableOpacity
+                <BadgeSelectItem
                   key={index}
-                  activeOpacity={0.7}
-                  style={[
-                    styles.ctypeView,
-                    index==this.state.filterIndex.connectorIndex && styles.actived
-                  ]}
-                  onPressOut={() => {
-                    this.selectConnector(index);
-                  }}>
+                  style={styles.ctypeView}
+                  borderColor={textCancel}
+                  checked={index==this.state.filterIndex.connectorIndex}
+                  onPress={() => this.selectConnector(index)}>
                   <Image 
                     style={styles.typeIcon}
                     source={item.icon}/>
                   <Text style={styles.typeName}>{item.name}</Text>
-                </TouchableOpacity>
+                </BadgeSelectItem>
               )
             })
           }
@@ -254,11 +251,17 @@ const styles = StyleSheet.create({
     textAlign: 'center',
   },
   labelText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 16,
     fontWeight: 'bold',
     paddingTop: 16,
-    paddingBottom: 8
+    paddingBottom: 8,
+    textShadowOffset: {
+      width: 0,
+      height: 1
+    },
+    textShadowRadius: 4,
+    textShadowColor: "rgba(0, 0, 0, 0.25)",
   },
   connectorView: {
     paddingTop: 8,
@@ -272,9 +275,9 @@ const styles = StyleSheet.create({
     marginRight: 16,
     marginBottom: 8,
     borderWidth: 1.5,
-    borderColor: '#999',
     alignItems: 'center',
-    textAlign: 'center'
+    /*...ElevationObject(5),
+    backgroundColor: colorLight*/
   },
   ptypeView: {
     minWidth: 54,
@@ -286,14 +289,16 @@ const styles = StyleSheet.create({
     borderRadius: 6,
     marginTop: 12,
     borderWidth: 1.5,
-    borderColor: '#999',
-    textAlign: 'center'
+    borderColor: textCancel,
+    textAlign: 'center',
+    /*...ElevationObject(5),
+    backgroundColor: colorLight*/
   },
   actived: {
-    borderColor: colorPrimary
+    borderColor: colorAccent
   },
   typeText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     textAlign: 'center'
   },

+ 3 - 3
Strides-APP/app/pages/home/maps/TopInfo.js

@@ -87,7 +87,7 @@ const styles = StyleSheet.create({
     position: 'absolute',
     alignItems: 'center',
     flexDirection: 'row',
-    backgroundColor: 'white',
+    backgroundColor: colorLight,
     ...ElevationObject(1.5)
   },
   stationInfo: {
@@ -132,7 +132,7 @@ const styles = StyleSheet.create({
     height: 23,
   },
   availableText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 13,
     textAlign: 'center'
   },
@@ -145,7 +145,7 @@ const styles = StyleSheet.create({
     justifyContent: 'space-between'
   },
   distanceText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 12,
   }
 })

+ 15 - 18
Strides-APP/app/pages/my/AddVehicle.js

@@ -5,8 +5,8 @@
 import React, { Component } from 'react';
 import { View, Text, StyleSheet, Pressable, Image, TextInput } from 'react-native';
 import apiUser from '../../api/apiUser';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
 import Dialog from '../../components/Dialog';
-import ChargeItemSelect from '../../icons/ChargeItemSelect';
 import { TypeImageList } from '../charge/Charging';
 
 export default class AddVehicle extends Component {
@@ -90,25 +90,22 @@ export default class AddVehicle extends Component {
           <View style={styles.connectorView}>
             { this.state.connectorType.map((item, index) => {
                 return (
-                  <Pressable
+                  <BadgeSelectItem
                     key={index}
-                    style={[
-                      styles.ctypeView,
-                      index==this.state.connectorIndex && styles.actived
-                    ]}
-                    onPress={() => {
-                      this.selectConnector(index);
-                    }}>
+                    style={styles.ctypeView}
+                    borderColor={textCancel}
+                    checked={index==this.state.connectorIndex}
+                    onPress={() => this.selectConnector(index)}>
                     <Image
                       style={styles.typeIcon}
                       source={item.icon}/>
                     <Text style={styles.typeName}>{item.name}</Text>
-                    { index==this.state.connectorIndex &&
+                    {/* index==this.state.connectorIndex &&
                       <View style={styles.checkedIcon}>
-                        <ChargeItemSelect size={12} selected={true} tint={colorPrimary}/>
+                        <ChargeItemSelect size={12} selected={true}/>
                       </View>
-                    }
-                  </Pressable>
+                    */}
+                  </BadgeSelectItem>
                 )
               })
             }
@@ -131,7 +128,7 @@ export default class AddVehicle extends Component {
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: 'white'
+    backgroundColor: pageBackground
   },
   formView: {
     paddingTop: 16,
@@ -140,12 +137,13 @@ const styles = StyleSheet.create({
     paddingBottom: 4
   },
   label: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14
   },
   formInput: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
+    height: 42,
     paddingBottom: 2,
     borderBottomColor: '#EEE',
     borderBottomWidth: 1
@@ -162,7 +160,6 @@ const styles = StyleSheet.create({
     marginRight: 16,
     marginBottom: 8,
     borderWidth: 1.5,
-    borderColor: '#999',
     alignItems: 'center',
     textAlign: 'center'
   },
@@ -178,7 +175,7 @@ const styles = StyleSheet.create({
     position: 'absolute',
   },
   actived: {
-    borderColor: colorPrimary
+    borderColor: colorAccent
   },
   typeName: {
     color: '#000',

+ 15 - 16
Strides-APP/app/pages/my/EditProfile.js

@@ -13,6 +13,7 @@ import { ModalProps } from '../../components/BottomModal';
 import Button, { ViewHeight } from '../../components/Button';
 import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import Dropdown from '../../components/Dropdown';
+import { UploadThemes } from '../../components/ThemesConfig';
 import utils from '../../utils/utils';
 import { DialogMaxWidth } from '../charge/InfoDialog';
 import { PageList } from '../Router';
@@ -26,9 +27,7 @@ const options = {
   mediaType: 'photo',
   compressImageQuality: 0.8,
   cropperCircleOverlay: true,
-  cropperStatusBarColor: colorAccent,
-  cropperToolbarColor: colorAccent,
-  cropperActiveWidgetColor: colorAccent
+  ...UploadThemes
 }
 
 //向右的箭头
@@ -107,15 +106,15 @@ const EditDialog = ({visible, title, value, keyType, countryList, areaNo, showCa
         <View style={styles.modalButtons}>
           <Button
             style={styles.btnCancel}
-            viewStyle={ViewHeight(48)}
-            textColor={colorPrimary}
+            viewStyle={ViewHeight(42)}
+            textColor={textCancel}
             text='Cancel'
             onClick={() => {
               onChangedText('');
             }}/>
           <Button
             style={styles.btnOk}
-            viewStyle={ViewHeight(48)}
+            viewStyle={ViewHeight(42)}
             text='Save'
             onClick={() => {
               onChangedText(changedText, callingCode);
@@ -343,7 +342,7 @@ export default class EditProfile extends Component {
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: 'white'
+    backgroundColor: pageBackground
   },
   profileView: {
     padding: 16,
@@ -356,7 +355,7 @@ const styles = StyleSheet.create({
   },
   infoText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingLeft: 16,
     textAlign: 'right'
@@ -374,7 +373,7 @@ const styles = StyleSheet.create({
     paddingBottom: 16,
     marginLeft: 'auto',
     marginRight: 'auto',
-    backgroundColor: 'white',
+    backgroundColor: colorLight,
     borderRadius: isIOS ? 20 : 3
   },
   editNickname: {
@@ -384,7 +383,7 @@ const styles = StyleSheet.create({
   },
   nickInput: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingTop: 8,
     paddingLeft: 16,
@@ -395,22 +394,22 @@ const styles = StyleSheet.create({
     backgroundColor: '#F6F6F6'
   },
   modalButtons: {
-    marginTop: 24,
-    paddingBottom: 16,
+    marginTop: 32,
+    paddingBottom: 12,
     alignItems: 'center',
     flexDirection: 'row'
   },
   btnCancel: {
     flex: 1,
     borderWidth: 1,
-    borderColor: colorPrimary,
-    backgroundColor: 'white'
+    borderColor: colorCancel,
+    backgroundColor: pageBackground
   },
   btnOk: {
     flex: 1,
     marginLeft: 16,
     borderWidth: 1,
-    borderColor: colorPrimary,
+    borderColor: colorAccent,
   },
   closeIcon: {
     top: 12,
@@ -427,7 +426,7 @@ const styles = StyleSheet.create({
     backgroundColor: '#F6F6F6'
   },
   selectText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 15,
     ...$padding(11, 10),
   },

+ 10 - 12
Strides-APP/app/pages/my/EditVehicle.js

@@ -5,6 +5,7 @@
 import React, { Component } from 'react';
 import { View, Text, Pressable, Image, TextInput } from 'react-native';
 import apiUser from '../../api/apiUser';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
 import Dialog from '../../components/Dialog';
 import ChargeItemSelect from '../../icons/ChargeItemSelect';
 import { TypeImageList } from '../charge/Charging';
@@ -156,25 +157,22 @@ export default class EditVehicle extends Component {
             <View style={styles.connectorView}>
               { this.state.connectorType.map((item, index) => {
                   return (
-                    <Pressable
+                    <BadgeSelectItem
                       key={index}
-                      style={[
-                        styles.ctypeView,
-                        index==this.state.connectorIndex && styles.actived
-                      ]}
-                      onPress={() => {
-                        this.selectConnector(index);
-                      }}>
+                      style={styles.ctypeView}
+                      borderColor={textCancel}
+                      checked={index==this.state.connectorIndex}
+                      onPress={() => this.selectConnector(index)}>
                       <Image
                         style={styles.typeIcon}
                         source={item.icon}/>
                       <Text style={styles.typeName}>{item.name}</Text>
-                      { index==this.state.connectorIndex &&
+                      {/* index==this.state.connectorIndex &&
                         <View style={styles.checkedIcon}>
-                          <ChargeItemSelect size={12} selected={true} tint={colorPrimary}/>
+                          <ChargeItemSelect size={12} selected={true}/>
                         </View>
-                      }
-                    </Pressable>
+                      */}
+                    </BadgeSelectItem>
                   )
                 })
               }

+ 98 - 73
Strides-APP/app/pages/my/Feedback.js

@@ -11,6 +11,7 @@ import apiUser from '../../api/apiUser';
 import Dialog from '../../components/Dialog';
 import ImagePicker from 'react-native-image-crop-picker';
 import Dropdown from '../../components/Dropdown';
+import { UploadThemes } from '../../components/ThemesConfig';
 
 const options = {
   cropping: false,
@@ -22,9 +23,7 @@ const options = {
   compressImageQuality: 0.8,
   compressImageMaxWidth: 720,
   compressImageMaxHeight: 1280,
-  cropperStatusBarColor: colorAccent,
-  cropperToolbarColor: colorAccent,
-  cropperActiveWidgetColor: colorAccent
+  ...UploadThemes
 }
 
 export default class Feedback extends React.Component {
@@ -43,7 +42,6 @@ export default class Feedback extends React.Component {
     this.pageShow = true;
     this.props.navigation.addListener('blur', () => {
       this.pageShow = false;
-      console.log('blur', this.pageShow);
     });
 
     apiUser.getTypeOfFeedback().then(res => {
@@ -129,68 +127,78 @@ export default class Feedback extends React.Component {
         style={styles.container}
         keyboardShouldPersistTaps='handled'
         contentInsetAdjustmentBehavior='automatic'>
-        <Text style={styles.title}>Have something to tell us?</Text>
-        <Text style={styles.content}>Please let us know below!</Text>
-
-        <Text style={styles.typeTitle}>Type of Feedback</Text>
-        <View style={styles.pickerView}>
-          <Dropdown
-            title='Type of Feedback'
-            list={this.state.typeList}
-            value={this.state.typeOfFeedback}
-            nameKey={'value'}
-            valueKey={'key'}
-            placeholder='Select'
-            onChange={(value, index)=> {
-              this.setState({
-                typeOfFeedback: value
-              })
-            }}/>
+        <View style={styles.headerView}>
+          <View style={ui.flex1}>
+            <Text style={styles.title}>Have something to tell us?</Text>
+            <Text style={styles.content}>Please let us know below!</Text>
+          </View>
+          <Image
+            style={styles.headerImage}
+            resizeMode="contain"
+            source={require('../../images/top-feedback.png')}/>
         </View>
         
-        <Text style={styles.typeTitle}>Please fill in here (500 words)</Text>
-        <TextInput
-          style={styles.feedbackInput}
-          multiline={true}
-          numberOfLines={8}
-          textAlignVertical='top'
-          onChangeText={text => {
-            this.setState({
-              feedback: text
-            });
-          }}/>
+        <View style={styles.contentView}>
+          <Text style={styles.typeTitle}>Type of Feedback</Text>
+          <View style={styles.pickerView}>
+            <Dropdown
+              title='Type of Feedback'
+              list={this.state.typeList}
+              value={this.state.typeOfFeedback}
+              nameKey={'value'}
+              valueKey={'key'}
+              placeholder='Select'
+              onChange={(value, index)=> {
+                this.setState({
+                  typeOfFeedback: value
+                })
+              }}/>
+          </View>
+          
+          <Text style={styles.typeTitle}>Please fill in here (500 words)</Text>
+          <TextInput
+            style={styles.feedbackInput}
+            multiline={true}
+            numberOfLines={8}
+            textAlignVertical='top'
+            onChangeText={text => {
+              this.setState({
+                feedback: text
+              });
+            }}/>
 
-        <View
-          style={styles.uploadGroup}>
-          { this.state.imageUrl.map((item, index) => {
-              return (
-                <Pressable
-                  key={index}
-                  style={styles.uploadView}
-                  onPress={() => {
-                    this.uploadImage(index)
-                  }}>
-                  { item == ''
-                  ? <Image
-                      style={styles.uploadIcon}
-                      source={require('../../images/icon/ic-add-photo.png')}/>
-                  : <Image
-                      style={styles.uploadIcon}
-                      defaultSource={require('../../images/icon/icon-upload-default.png')}
-                      source={{uri: host + item}}/>
-                  }
-                </Pressable>
-              );
-            })
-          }
+          <View
+            style={styles.uploadGroup}>
+            { this.state.imageUrl.map((item, index) => {
+                return (
+                  <Pressable
+                    key={index}
+                    style={styles.uploadView}
+                    onPress={() => {
+                      this.uploadImage(index)
+                    }}>
+                    { item == ''
+                    ? <Image
+                        style={styles.uploadIcon}
+                        source={require('../../images/icon/ic-add-photo.png')}/>
+                    : <Image
+                        style={styles.uploadIcon}
+                        defaultSource={require('../../images/icon/icon-upload-default.png')}
+                        source={{uri: host + item}}/>
+                    }
+                  </Pressable>
+                );
+              })
+            }
+          </View>
+          <Button
+            style={styles.button}
+            text='Submit Feedback'
+            elevation={1.5}
+            onClick={() => {
+              this.submitFeedback();
+            }}/>
         </View>
-        <Button
-          style={styles.button}
-          text='Submit Feedback'
-          elevation={1.5}
-          onClick={() => {
-            this.submitFeedback();
-          }}/>
       </ScrollView>
     );
   }
@@ -199,29 +207,46 @@ export default class Feedback extends React.Component {
 const styles = StyleSheet.create({
   container: {
     flex: 1,
+    backgroundColor: pageBackground
+  },
+  contentView: {
+    marginTop: -30,
+    borderTopLeftRadius: 30,
+    borderTopRightRadius: 30,
+    ...$padding(8, 16, 16),
+    backgroundColor: colorLight
+  },
+  headerView: {
+    paddingTop: 16,
     paddingLeft: 16,
-    paddingRight: 16,
-    backgroundColor: 'white'
+    paddingRight: 8,
+    paddingBottom: 30,
+    flexDirection: 'row',
+    backgroundColor: '#F5F5F5'
+  },
+  headerImage: {
+    width: 123,
+    height: 101
   },
   title: {
-    color: '#000',
+    color: '#056A94',
     fontSize: 18,
     paddingTop: 16,
-    paddingBottom: 10
+    paddingBottom: 8
   },
   content: {
-    color: '#333',
-    fontSize: 14,
-    paddingBottom: 14
+    color: '#056A94',
+    fontSize: 14
   },
   typeTitle: {
     color: '#000',
-    fontSize: 14,
+    fontSize: 16,
     paddingTop: 16,
+    fontWeight: 'bold',
     paddingBottom: 10
   },
   pickerView: {
-    width: $vw(60),
+    //width: $vw(60),
     height: 44,
     borderWidth: 1,
     borderColor: '#999',
@@ -233,7 +258,7 @@ const styles = StyleSheet.create({
     backgroundColor: '#F5F5F5'
   },
   feedbackInput: {
-    color: '#333',
+    color: textPrimary,
     minHeight: 100,
     borderWidth: 1,
     borderColor: '#999',
@@ -258,7 +283,7 @@ const styles = StyleSheet.create({
     borderRadius: 6
   },
   button: {
-    marginTop: 72,
+    marginTop: 32,
     marginBottom: 16,
     borderRadius: 4
   }

+ 457 - 0
Strides-APP/app/pages/my/ProfileV2.js

@@ -0,0 +1,457 @@
+/**
+ * Profile Settings页面
+ * @邠心vbe on 2021/04/27
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, Image, ScrollView, Switch, Pressable } from 'react-native';
+import apiUser from '../../api/apiUser';
+import { host, setAccessToken } from '../../api/http';
+import Button, { ElevationObject } from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import { getStorageJsonSync, setStorage, setStorageJson } from '../../utils/storage';
+import utils from '../../utils/utils';
+import { PageList } from '../Router';
+
+export default class ProfileV2 extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      userInfo: userInfo,
+      refreshId: 0,
+      totalVehicle: 0
+    };
+  }
+
+  componentDidMount() {
+    this.init();
+    this.props.navigation.addListener('focus', () => {
+      this.init();
+    });
+  }
+
+  init() {
+    getUserInfo(info => {
+      this.setState({
+        userInfo: info,
+        refreshId: this.state.refreshId + 1
+      });
+    }, true);
+  }
+
+  removeVehicle(id) {
+    Dialog.showDialog({
+      title: 'Remove Vehicle',
+      message: 'Are you sure you want to remove this vehicle?',
+      callback: btn => {
+        if (btn == 'ok') {
+          Dialog.dismissLoading();
+          this.deleteVehicle(id);
+        }
+      }
+    })
+  }
+
+  deleteVehicle(id) {
+    Dialog.showProgressDialog();
+    apiUser.deleteVehicle({
+      vehiclePk: id
+    }).then(res => {
+      Dialog.dismissLoading();
+      toastShort('Delete successfully');
+      this.setState({
+        refreshId: this.state.refreshId + 1
+      })
+    }).catch(err => {
+      Dialog.dismissLoading();
+      toastShort(err);
+    });
+  }
+
+  deleteAccount() {
+    Dialog.showDialog({
+      title: 'Delete Account',
+      message: 'Are you sure you want to delete your account? This operation cannot be revoke.',
+      ok: 'CONFIRM',
+      callback: button => {
+        if (button == Dialog.BUTTON_OK) {
+          this.deleteMyAccount();
+        }
+      }
+    })
+  }
+
+  deleteMyAccount() {
+    Dialog.showProgressDialog();
+    apiUser.deleteAccount().then(res => {
+      toastShort('Successfully deleted!')
+      Dialog.dismissLoading();
+      setTimeout(() => {
+        startPage(PageList.login);
+      }, 500);
+    }).catch(err => {
+      Dialog.dismissLoading();
+      toastShort(err)
+    })
+  }
+
+  logout() {
+    Dialog.showDialog({
+      title: 'Sign out',
+      message: 'Are you sure you want to sign out?',
+      callback: btn => {
+        if (btn == 'ok') {
+          Dialog.showProgressDialog();
+          setTimeout(() => {
+            this.requestLogout();
+          }, 500);
+        }
+      }
+    })
+  }
+
+  async requestLogout() {
+    const data = await getStorageJsonSync('loginData');
+    if (data && data.email) {
+      delete data.password
+      setStorageJson('loginData', data);
+      setStorage('RegisterTokenDate', "");
+    }
+    global.userInfo = {}
+    /*this.setState({
+      isLogin: false,
+      userInfo: {}
+    });*/
+    setAccessToken('');
+    goBack();
+    Dialog.dismissLoading();
+  }
+
+  render() {
+    return (
+      <ScrollView style={styles.container}>
+        {/* Profile Info */}
+        <View style={styles.cardView}>
+          <Pressable
+            style={styles.profileView}
+            onPress={() => startPage(PageList.editProfile)}>
+            { this.state.userInfo.photoUrl
+            ? <Image
+                style={styles.avatar}
+                source={{uri: host + this.state.userInfo.photoUrl}}/>
+            : <Image
+                style={styles.avatar}
+                source={require('../../images/user/ic-avatar-default.png')}/>
+            }
+            <View style={styles.infoContent}>
+              <Text
+                style={styles.nickname}
+                ellipsizeMode='tail'
+                numberOfLines={1}>{this.state.userInfo.nickName}</Text>
+              <Text style={styles.userText}>{this.state.userInfo.email}</Text>
+              <Text style={styles.userText}>{(utils.isNotEmpty(this.state.userInfo.callingCode) && "+" + this.state.userInfo.callingCode + " ") + this.state.userInfo.phone}</Text>
+            </View>
+            <FontAwesome
+              size={34}
+              color={textCancel}
+              name='angle-right'/>
+          </Pressable>
+        </View>
+        {/* Wallet Info */}
+        <Pressable 
+          style={styles.cardView}
+          onPress={() => startPage(PageList.wallet)}>
+          <Image
+            style={styles.cardIcon}
+            source={require('../../images/user/card-wallet.png')}/>
+          <View style={styles.cardInfo}>
+            <Text style={styles.cardLabel}>Credit Wallet: </Text>
+            <Text style={styles.cardPrimary}>{currency}{this.state.userInfo.credit}</Text>
+          </View>
+          <FontAwesome
+            size={28}
+            color={textCancel}
+            name='angle-right'/>
+        </Pressable>
+        {/* Vehicle Info */}
+        <Pressable
+          style={styles.cardView}
+          onPress={() => startPage(PageList.myVehicles)}>
+          <Image
+            style={styles.cardIcon}
+            source={require('../../images/user/card-vehicle.png')}/>
+          <View style={styles.cardInfo}>
+            <Text style={styles.cardLabel}>My Vehicles: </Text>
+            <Text style={styles.cardPrimary}>{this.state.userInfo.countVehicle}</Text>
+          </View>
+          <FontAwesome
+            size={28}
+            color={textCancel}
+            name='angle-right'/>
+        </Pressable>
+        {/* Vehicle List */}
+        {/* <View style={styles.titleView}>
+          <Text style={styles.title}>My Vehicles</Text>
+          <Button
+            style={{backgroundColor: colorLight}}
+            borderRadius={3}
+            viewStyle={styles.titleAdd}
+            onClick={() => {
+              startPage(PageList.addVehicle)
+            }}>
+            <Entypo name='plus' size={14} color={colorDark}/>
+            <Text style={{fontSize: 12, paddingLeft: 1, color: textPrimary}}>Add Vehicle</Text>
+          </Button>
+        </View> */}
+        {/* <View style={styles.verhicleList}>
+          <VehicleList
+            refreshId={this.state.refreshId}
+            onResult={count => {
+              this.setState({
+                totalVehicle: count
+              })
+            }}
+            onDelete={id => this.removeVehicle(id)}
+          />
+        </View> */}
+        {/* Account List */}
+        {/* <View style={styles.titleView}>
+          <Text style={styles.title}>My Cards</Text>
+          <Button
+            textColor={textPrimary}
+            style={styles.titleAdd}
+            onClick={() => {
+              startPage(PageList.addCard)
+            }}>
+            <Entypo name='plus' size={14} color={colorDark}/>
+            <Text style={{fontSize: 12, paddingLeft: 1}}>Add Card</Text>
+          </Button>
+        </View>
+        <View style={styles.accountList}>
+          <CardList refreshId={this.state.refreshId}/>
+        </View> */}
+        <Pressable
+          style={styles.cardView}
+          onPress={() => startPage(PageList.changePassword)}>
+          <Image
+            style={styles.cardIcon}
+            source={require('../../images/user/card-account.png')}/>
+          <View style={styles.cardInfo}>
+            <Text style={styles.cardLabel}>Account Security</Text>
+          </View>
+          <FontAwesome
+            size={28}
+            color={textCancel}
+            name='angle-right'/>
+        </Pressable>
+        {/* Notifications */}
+        <Pressable
+          style={styles.cardView}
+          onPress={() => startPage(PageList.settings)}>
+          <Image
+            style={styles.cardIcon}
+            source={require('../../images/user/card-notification.png')}/>
+          <View style={styles.cardInfo}>
+            <Text style={styles.cardLabel}>Notification Settings</Text>
+          </View>
+          <FontAwesome
+            size={28}
+            color={textCancel}
+            name='angle-right'/>
+        </Pressable>
+        { this.state.userInfo.userType == "Public" &&
+          <Pressable
+            style={styles.cardView}
+            onPress={() => startPage(PageList.registerFleet)}>
+            <MaterialCommunityIcons
+              style={styles.cardIcon}
+              name="credit-card-edit"
+              size={32}
+              color="#00638C"/>
+            <View style={styles.cardInfo}>
+              <Text style={styles.cardLabel}>Apply as PHV/Fleet</Text>
+            </View>
+            <FontAwesome
+              size={28}
+              color={textCancel}
+              name='angle-right'/>
+          </Pressable>
+        }
+        <Pressable
+          style={styles.cardView}
+          onPress={() => this.deleteAccount()}>
+          <MaterialCommunityIcons
+            style={styles.cardIcon}
+            name="account-remove"
+            size={32}
+            color="#00638C"/>
+          <View style={styles.cardInfo}>
+            <Text style={styles.cardLabel}>Delete Account</Text>
+          </View>
+          <FontAwesome
+            size={28}
+            color={textCancel}
+            name='angle-right'/>
+        </Pressable>
+        {/* <Button
+          style={styles.deleteButton}
+          text="DELETE MY ACCOUNT"
+          textColor={textButton}
+          onClick={() => this.deleteAccount()}
+        /> */}
+        <Button
+          style={styles.deleteButton}
+          text="Logout"
+          textColor={textButton}
+          onClick={() => this.logout()}
+        />
+      </ScrollView>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: pageBackground
+  },
+  headerView: {
+    paddingBottom: 72
+  },
+  background: {
+    left: 0,
+    right: 0,
+    bottom: 0,
+    height: $vw(62.4),
+    alignItems: 'center',
+    position: 'absolute'
+  },
+  profileView: {
+    zIndex: 10,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  avatar: {
+    width: 66,
+    height: 66,
+    borderWidth: 2,
+    borderRadius: 80,
+    borderColor: "#00638C"
+  },
+  infoContent: {
+    flex: 1,
+    paddingLeft: 16,
+  },
+  nickname: {
+    color: '#000',
+    fontSize: 20,
+    fontWeight: 'bold',
+    paddingTop: 1,
+    paddingBottom: 1,
+  },
+  userText: {
+    color: textPrimary,
+    fontSize: 13,
+    paddingTop: 1.5
+  },
+  cardView: {
+    padding: 16,
+    marginTop: 16,
+    marginLeft: 16,
+    marginRight: 16,
+    borderRadius: 10,
+    ...ElevationObject(5),
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: colorLight,
+  },
+  cardItem: {
+    flex: 1,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center'
+  },
+  cardDivide: {
+    borderLeftWidth: 1,
+    borderLeftColor: '#EEE'
+  },
+  cardIcon: {
+    width: 32,
+    height: 32
+  },
+  cardInfo: {
+    flex: 1,
+    paddingLeft: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  cardPrimary: {
+    color: textPrimary,
+    fontSize: 18,
+    paddingBottom: 2
+  },
+  cardLabel: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  titleView: {
+    padding: 12,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  title: {
+    flex: 1,
+    padding: 8,
+    color: '#000',
+    fontSize: 16,
+  },
+  titleAdd: {
+    padding: 8,
+    color: textPrimary,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  verhicleList: {
+    paddingLeft: 16,
+    paddingRight: 16,
+    marginBottom: -16
+  },
+  accountList: {
+    paddingLeft: 16,
+    paddingRight: 16,
+    marginBottom: -16
+  },
+  notificationView: {
+    marginLeft: 16,
+    marginRight: 16,
+    marginBottom: 20,
+    borderRadius: 10,
+    overflow: 'hidden',
+    paddingLeft: 16,
+    paddingRight: 16,
+    borderColor: '#f5f5f5',
+    borderWidth: 1,
+    backgroundColor: colorLight,
+    ...ElevationObject(1.5)
+  },
+  notificationItem: {
+    paddingTop: 16,
+    paddingBottom: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  notiLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14
+  },
+  divide: {
+    borderTopWidth: 1,
+    borderTopColor: '#eee'
+  },
+  deleteButton: {
+    ...$margin(48, 16, 16),
+    backgroundColor: '#EA0A2A'
+  }
+})
+ 

+ 97 - 17
Strides-APP/app/pages/my/VehicleList.js

@@ -6,6 +6,8 @@ import React, { Component } from 'react';
 import { View, Text, ImageBackground, Image, StyleSheet, Pressable } from 'react-native';
 import apiUser from '../../api/apiUser';
 import { ElevationObject } from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import VehicleType from '../../icons/VehicleType';
 import { getConnectTypeByKey } from '../charge/Charging';
 import { PageList } from '../Router';
 
@@ -20,6 +22,9 @@ export default class VehicleList extends Component {
 
   componentDidMount() {
     this.getVehicleList();
+    this.props.navigation.addListener('focus', () => {
+      this.getVehicleList();
+    })
   }
 
   componentDidUpdate() {
@@ -51,11 +56,38 @@ export default class VehicleList extends Component {
     });
   }
 
+  removeVehicle(id) {
+    Dialog.showDialog({
+      title: 'Remove Vehicle',
+      message: 'Are you sure you want to remove this vehicle?',
+      callback: btn => {
+        if (btn == 'ok') {
+          Dialog.dismissLoading();
+          this.deleteVehicle(id);
+        }
+      }
+    })
+  }
+
+  deleteVehicle(id) {
+    Dialog.showProgressDialog();
+    apiUser.deleteVehicle({
+      vehiclePk: id
+    }).then(res => {
+      Dialog.dismissLoading();
+      toastShort('Delete successfully');
+      this.getVehicleList();
+    }).catch(err => {
+      Dialog.dismissLoading();
+      toastShort(err);
+    });
+  }
+
   render() {
     return (
       this.state.vehicleList.length > 0
-      ?  this.state.vehicleList.map((item, index) => {
-        const type = getConnectTypeByKey(item.connectorType)
+      ? this.state.vehicleList.map((item, index) => {
+        //const type = getConnectTypeByKey(item.connectorType)
         return (
           <Pressable
             key={index}
@@ -65,10 +97,14 @@ export default class VehicleList extends Component {
             onLongPress={() => {
               if (this.props.onDelete) {
                 this.props.onDelete(item.vehiclePk)
+              } else {
+                this.removeVehicle(item.vehiclePk)
               }
-            }}>
+            }}
+            style={$padding(16, 16, 0)}>
             <ImageBackground
               style={styles.vehicleView}
+              resizeMode="contain"
               source={require('../../images/user/bg-vehicles.png')}>
               <View style={styles.vehicleRow}>
                 <Image
@@ -78,11 +114,27 @@ export default class VehicleList extends Component {
               </View>
               <Text style={styles.vehicleModle}>{item.licensePlate}</Text>
               <View style={styles.vehicleRow}>
-                <Image
+                <View style={styles.vehicleTypeRow}>
+                  <View style={styles.vehicleTypeIcon}>
+                    <VehicleType size={10}/>
+                  </View>
+                  <Text style={styles.vehicleType}>TYPE {item.connectorType}</Text>
+                </View>
+                {/* <Image
                   style={styles.vehicleIcon}
                   source={type?.icon}/>
-                <Text style={styles.vehicleName}>{type?.name}</Text>
+                <Text style={styles.vehicleName}>{type?.name}</Text> */}
               </View>
+              <Pressable
+                style={styles.closeIcon}
+                android_ripple={rippleLess}
+                onPress={() => this.removeVehicle(item.vehiclePk)}>
+                <MaterialCommunityIcons
+                  name="close"
+                  size={20}
+                  color={textCancel}
+                />
+              </Pressable>
             </ImageBackground>
           </Pressable>
         );
@@ -94,30 +146,58 @@ export default class VehicleList extends Component {
 
 const styles = StyleSheet.create({
   vehicleView: {
-    padding: 20,
-    borderRadius: 10,
+    ...$padding(12, 16, 20),
+    borderRadius: 8,
     overflow: 'hidden',
-    marginBottom: 16,
-    ...ElevationObject(1.5)
+    ...ElevationObject(5)
   },
   vehicleRow: {
     alignItems: 'center',
     flexDirection: 'row',
   },
   vehicleIcon: {
-    width: 20,
-    height: 20,
+    width: 32,
+    height: 32,
   },
   vehicleName: {
-    color: '#000',
-    fontSize: 14,
-    paddingLeft: 12
+    color: textDark,
+    fontSize: 20,
+    paddingLeft: 12,
+    fontWeight: 'bold'
   },
   vehicleModle: {
-    color: '#333',
-    fontSize: 12,
-    paddingLeft: 32,
+    color: textPrimary,
+    fontSize: 16,
+    paddingLeft: 44,
     paddingTop: 2,
     paddingBottom: 7
   },
+  vehicleTypeRow: {
+    height: 20,
+    marginLeft: 44,
+    borderRadius: 4,
+    overflow: 'hidden',
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: '#E5EFDE'
+  },
+  vehicleTypeIcon: {
+    width: 20,
+    height: 20,
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: colorAccent
+  },
+  vehicleType: {
+    color: colorAccent,
+    fontSize: 13,
+    paddingLeft: 6,
+    paddingRight: 8,
+  },
+  closeIcon: {
+    top: 0,
+    right: 0,
+    padding: 12,
+    position: 'absolute'
+  }
 });

+ 46 - 0
Strides-APP/app/pages/search/ConnectType.js

@@ -0,0 +1,46 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+
+export default ConnectType = ({type, available, all, color=textPrimary}) => {
+  if (type) {
+    return (
+      <View style={[styles.connectType, {borderColor: color}]}>
+        <Text style={[styles.typeLabel, {backgroundColor: color}]}>{type}</Text>
+        <Text style={[styles.typeContent, {color: color}]}><Text style={styles.typeBold}>{available}</Text>/{all}</Text>
+      </View>
+    );
+  } else {
+    return <></>;
+  }
+}
+
+const styles = StyleSheet.create({
+  connectType: {
+    borderWidth: 1,
+    borderColor: textPrimary,
+    borderRadius: 3,
+    marginRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row',
+  },
+  typeLabel: {
+    color: textLight,
+    fontSize: 12,
+    paddingTop: 2,
+    paddingLeft: 10,
+    paddingRight: 10,
+    paddingBottom: 2,
+    backgroundColor: textPrimary
+  },
+  typeContent: {
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 10,
+    paddingRight: 6,
+  },
+  typeBold: {
+    color: textPrimary,
+    fontSize: 14,
+    fontWeight: 'bold'
+  }
+})

+ 129 - 0
Strides-APP/app/pages/search/ListView.js

@@ -0,0 +1,129 @@
+/**
+ * 搜索列表复用组件
+ * @邠心vbe on 2023/02/03
+ */
+import React from 'react';
+import { Pressable, StyleSheet, Text, View } from 'react-native';
+import TextRadius from '../../components/TextRadius';
+import utils from '../../utils/utils';
+import Provider from '../charge/Provider';
+import ConnectType from './ConnectType';
+
+export default ListView = ({item, index, separators, onPress}) => {
+  if (item.id) {
+    return (
+      <View 
+        style={styles.itemView}
+        key={index}>
+        <Pressable
+          style={styles.stationInfo}
+          onPress={onPress}>
+          <View style={styles.nameView}>
+            <Text style={styles.stationName}>{item.name}</Text>
+            { item.allConnector && item.allConnector.available > 0 &&
+              <TextRadius style={[styles.infoStatus, styles.available]}>Available</TextRadius>
+            }
+          </View>
+          <Provider providers={item.serviceProvider}/>
+          <Text style={styles.stationAddress}>{item.address}</Text>
+          <View style={styles.connectView}>
+            <ConnectType {...item.acConnector}/>
+            <ConnectType {...item.dcConnector}/>
+          </View>
+        </Pressable>
+        <Pressable 
+          style={styles.directView}
+          onPress={() => {
+            utils.directMaps(item.latitude, item.longitude, item.address);
+          }}>
+          <MaterialIcons
+            name='directions'
+            size={32}
+            color={colorAccent}/>
+          <Text style={styles.distanceText}>{item.distance}</Text>
+        </Pressable>
+      </View>
+    );
+  } else {
+    return <></>;
+  }
+}
+
+const styles = StyleSheet.create({
+  itemView: {
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee'
+  },
+  stationInfo: {
+    flex: 1,
+    padding: 16
+  },
+  nameView: {
+    paddingTop: 3,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  stationName: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  stationAddress: {
+    color: '#666',
+    fontSize: 14,
+    paddingBottom: 8
+  },
+  infoStatus: {
+    fontSize: 10,
+    paddingTop: 3,
+    paddingLeft: 8,
+    paddingRight: 8,
+    paddingBottom: 3,
+    borderRadius: 5,
+    marginLeft: 12,
+  },
+  selected: {
+    color: textPrimary,
+    backgroundColor: colorAccent
+  },
+  available: {
+    color: textLight,
+    backgroundColor: '#90DB0A'
+  },
+  unavailable: {
+    color: '#999',
+    fontSize: 10.5,
+    paddingTop: 7,
+    paddingLeft: 9,
+    paddingRight: 9,
+    paddingBottom: 7,
+    backgroundColor: '#CCC'
+  },
+  connectView: {
+    paddingTop: 4,
+    paddingBottom: 4,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  connectType: {
+    borderWidth: 1,
+    borderColor: textPrimary,
+    borderRadius: 3,
+    marginRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row',
+  },
+  directView: {
+    zIndex: 1,
+    paddingTop: 4,
+    paddingRight: 16,
+    alignItems: 'center'
+  },
+  distanceText: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 2
+  },
+})

+ 142 - 0
Strides-APP/app/pages/search/ListViewV2.js

@@ -0,0 +1,142 @@
+/**
+ * 新版搜索列表复用组件
+ * @邠心vbe on 2023/02/03
+ */
+import React from 'react';
+import { Pressable, StyleSheet, Text, View } from 'react-native';
+import TextRadius from '../../components/TextRadius';
+import utils from '../../utils/utils';
+import Provider from '../charge/Provider';
+import ConnectType from './ConnectType';
+
+export default ListViewV2 = ({item, index, separators, onPress}) => {
+  if (item.id) {
+    return (
+      <Pressable 
+        style={styles.itemView}
+        key={index}
+        onPress={onPress}
+        android_ripple={ripple}>
+        <Ionicons
+          name="md-location-sharp"
+          size={20}
+          color="#E5E5E5"
+        />
+        <View style={styles.stationInfo} >
+          <Text style={styles.stationName}>{item.name}</Text>
+          <Text style={styles.stationAddress}>{item.address}</Text>
+          {/* <Provider providers={item.serviceProvider}/> */}
+          <View style={styles.connectView}>
+            <ConnectType color={textCancel} {...item.acConnector}/>
+            <ConnectType color={textCancel} {...item.dcConnector}/>
+          </View>
+          <View style={ui.flexc}>
+            <TextRadius style={[styles.infoStatus, styles.available]}>{item.distance}</TextRadius>
+            {item.allConnector && item.allConnector.available > 0 &&
+              <TextRadius style={[styles.infoStatus, styles.available]}>Available</TextRadius>
+            }
+            {item.siteType == "Private" &&
+              <TextRadius style={[styles.infoStatus, styles.private]}>Private Site</TextRadius>
+            }
+          </View>
+        </View>
+        <Pressable 
+          style={styles.directView}
+          onPress={() => {
+            utils.directMaps(item.latitude, item.longitude, item.address);
+          }}>
+          <MaterialIcons
+            name='directions'
+            size={32}
+            color={colorAccent}/>
+        </Pressable>
+      </Pressable>
+    );
+  } else {
+    return <></>;
+  }
+}
+
+const styles = StyleSheet.create({
+  itemView: {
+    padding: 16,
+    flexDirection: 'row',
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee'
+  },
+  stationInfo: {
+    flex: 1,
+    paddingLeft: 8
+  },
+  nameView: {
+    paddingTop: 3,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  stationName: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  stationAddress: {
+    color: textCancel,
+    fontSize: 14,
+    paddingTop: 4,
+    paddingBottom: 4
+  },
+  infoStatus: {
+    fontSize: 12,
+    borderRadius: 3,
+    marginRight: 5,
+    borderWidth: 1,
+    ...$padding(3, 8, 2)
+  },
+  selected: {
+    color: textPrimary,
+    borderColor: colorAccent
+  },
+  available: {
+    color: '#90DB0A',
+    borderColor: '#90DB0A'
+  },
+  unavailable: {
+    color: '#999',
+    fontSize: 10.5,
+    paddingTop: 7,
+    paddingLeft: 9,
+    paddingRight: 9,
+    paddingBottom: 7,
+    backgroundColor: '#CCC'
+  },
+  private: {
+    color: '#FDB702',
+    borderColor: '#FDB702'
+  },
+  connectView: {
+    paddingTop: 4,
+    paddingBottom: 8,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  connectType: {
+    borderWidth: 1,
+    borderColor: textPrimary,
+    borderRadius: 3,
+    marginRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row',
+  },
+  directView: {
+    zIndex: 1,
+    width: 32,
+    height: 32,
+    marginTop: 4,
+    marginLeft: 8,
+    alignItems: 'center'
+  },
+  distanceText: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 2
+  },
+})

+ 238 - 0
Strides-APP/app/pages/search/Search.js

@@ -0,0 +1,238 @@
+/**
+ * 搜索页
+ * @邠心vbe on 2021/04/12
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, TextInput, FlatList, Image } from 'react-native';
+import apiStation from '../../api/apiStation';
+import utils from '../../utils/utils';
+import { PageList } from '../Router';
+import ListView from './ListView';
+
+export default class Search extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isSearch: false,
+      searchResult: [{id:0}]
+    };
+  }
+
+  componentDidMount() {
+    this.getGeoLocation();
+  }
+
+  getGeoLocation() {
+    navigator.geolocation.getCurrentPosition(location => {
+      let latlng = {
+        lat: location.coords.latitude,
+        lng: location.coords.longitude
+      }
+      this.searchStation(latlng);
+    }, error => {
+      console.warn("getGeoLocation", error);
+    });
+  }
+
+  searchStation(latlng) {
+    this.setState({
+      isSearch: true
+    });
+    latlng.siteName = this.searchWorld
+    apiStation.searchStation(latlng).then(res => {
+      if (res.data.sites) {
+        const list = [];
+        res.data.sites.forEach(item => {
+          list.push({
+            id: item.sitePk,
+            name: item.siteName,
+            address: item.siteAddress,
+            latitude: item.locationLatitude,
+            longitude: item.locationLongitude,
+            acConnector: item.acConnector,
+            allConnector: item.allConnector,
+            dcConnector: item.dcConnector,
+            siteType: item.siteType,
+            distance: utils.getDistance(item.distance),
+            serviceProvider: item.serviceProvider
+          });
+        });
+        this.setState({
+          isSearch: false,
+          searchResult: list
+        });
+      }
+    }).catch(err => {
+      console.log('err', err);
+      this.setState({
+        isSearch: false,
+        searchResult: []
+      });
+    });
+  }
+
+  intoStation(info) {
+    startPage(PageList.chargeDetail, {stationInfo: info, action: 'search'});
+  }
+
+  listItem = (props) => {
+    return <ListView {...props} onPress={() => this.intoStation(props.item)}/>
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <View style={styles.searchView}>
+          <Feather
+            name={'search'}
+            size={20}
+            color={'#999'}/>
+          <TextInput
+            style={styles.searchInput}
+            autoFocus={true}
+            maxLength={50}
+            numberOfLines={1}
+            returnKeyType={'search'}
+            clearButtonMode={'while-editing'}
+            placeholder='Search using site name or service provider'
+            onChangeText={text => {
+              this.searchWorld = text;
+            }}
+            onSubmitEditing={() => {
+              this.getGeoLocation();
+            }}/>
+        </View>
+        { this.state.isSearch
+        ? <View style={styles.searchingView}>
+            <Image
+              style={styles.seachingIcon}
+              source={require('../../images/icon/loading.gif')}/>
+          </View>
+        : <FlatList
+            style={styles.listView}
+            data={this.state.searchResult}
+            renderItem={this.listItem}
+            keyExtractor={item => item.id}
+            ListEmptyComponent={<Text style={styles.noResult}>No search result</Text>}
+          />
+        }
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  searchView: {
+    marginTop: 16,
+    marginLeft: 16,
+    marginRight: 16,
+    marginBottom: 8,
+    paddingLeft: 16,
+    paddingRight: 16,
+    borderRadius: 60,
+    borderWidth: 1,
+    borderColor: '#E5E5E5',
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: '#F5F5F5'
+  },
+  searchInput: {
+    flex: 1,
+    color: textPrimary,
+    ...$padding(6, 8),
+    fontSize: 15,
+    marginLeft: 4,
+    lineHeight: 20
+  },
+  searchingView: {
+    padding: 16,
+    alignItems: 'center'
+  },
+  seachingIcon: {
+    width: 60,
+    height: 60
+  },
+  noResult: {
+    color: '#999',
+    fontSize: 14,
+    padding: 20,
+    textAlign: 'center',
+  },
+  listView: {
+    flex: 1
+  },
+  itemView: {
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee'
+  },
+  stationInfo: {
+    flex: 1,
+    padding: 16
+  },
+  nameView: {
+    paddingTop: 3,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  stationName: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  stationAddress: {
+    color: '#666',
+    fontSize: 14,
+    paddingBottom: 8
+  },
+  infoStatus: {
+    fontSize: 10,
+    paddingTop: 3,
+    paddingLeft: 8,
+    paddingRight: 8,
+    paddingBottom: 3,
+    borderRadius: 5,
+    marginLeft: 12,
+  },
+  selected: {
+    color: textPrimary,
+    backgroundColor: colorAccent
+  },
+  available: {
+    color: textLight,
+    backgroundColor: '#90DB0A'
+  },
+  unavailable: {
+    color: '#999',
+    fontSize: 10.5,
+    paddingTop: 7,
+    paddingLeft: 9,
+    paddingRight: 9,
+    paddingBottom: 7,
+    backgroundColor: '#CCC'
+  },
+  connectView: {
+    paddingTop: 4,
+    paddingBottom: 4,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+ 
+  
+  directView: {
+    zIndex: 1,
+    paddingTop: 4,
+    paddingRight: 16,
+    alignItems: 'center'
+  },
+  distanceText: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 2
+  },
+});

+ 242 - 0
Strides-APP/app/pages/search/SearchV2.js

@@ -0,0 +1,242 @@
+/**
+ * 新版搜索页
+ * @邠心vbe on 2023/02/03
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, TextInput, FlatList, Image } from 'react-native';
+import apiStation from '../../api/apiStation';
+import utils from '../../utils/utils';
+import { PageList } from '../Router';
+import ListViewV2 from './ListViewV2';
+
+export default class SearchV2 extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isSearch: false,
+      searchResult: [{id:0}]
+    };
+  }
+
+  componentDidMount() {
+    this.getGeoLocation();
+  }
+
+  getGeoLocation() {
+    navigator.geolocation.getCurrentPosition(location => {
+      let latlng = {
+        lat: location.coords.latitude,
+        lng: location.coords.longitude
+      }
+      this.searchStation(latlng);
+    }, error => {
+      console.warn("getGeoLocation", error);
+    });
+  }
+
+  searchStation(latlng) {
+    this.setState({
+      isSearch: true
+    });
+    latlng.siteName = this.searchWorld
+    apiStation.searchStation(latlng).then(res => {
+      if (res.data.sites) {
+        const list = [];
+        res.data.sites.forEach(item => {
+          list.push({
+            id: item.sitePk,
+            name: item.siteName,
+            address: item.siteAddress,
+            latitude: item.locationLatitude,
+            longitude: item.locationLongitude,
+            acConnector: item.acConnector,
+            allConnector: item.allConnector,
+            dcConnector: item.dcConnector,
+            siteType: item.siteType,
+            distance: utils.getDistance(item.distance),
+            serviceProvider: item.serviceProvider
+          });
+        });
+        setTimeout(() => {
+          this.setState({
+            isSearch: false,
+            searchResult: list
+          });
+        }, 1000);
+        
+      }
+    }).catch(err => {
+      console.log('err', err);
+      this.setState({
+        isSearch: false,
+        searchResult: []
+      });
+    });
+  }
+
+  intoStation(info) {
+    startPage(PageList.chargeDetailPage, {stationInfo: info, action: 'search', from: PageList.search});
+  }
+
+  listItem = (props) => {
+    return <ListViewV2 {...props} onPress={() => this.intoStation(props.item)}/>
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <View style={styles.searchView}>
+          <Feather
+            name={'search'}
+            size={20}
+            color={'#999'}/>
+          <TextInput
+            style={styles.searchInput}
+            autoFocus={true}
+            maxLength={50}
+            numberOfLines={1}
+            returnKeyType={'search'}
+            clearButtonMode={'while-editing'}
+            placeholder='Search using site name or service provider'
+            onChangeText={text => {
+              this.searchWorld = text;
+            }}
+            onSubmitEditing={() => {
+              this.getGeoLocation();
+            }}/>
+        </View>
+        { this.state.isSearch
+        ? <View style={styles.searchingView}>
+            <Image
+              style={styles.seachingIcon}
+              source={require('../../images/icon/search-loading.gif')}/>
+          </View>
+        : <FlatList
+            style={styles.listView}
+            data={this.state.searchResult}
+            renderItem={this.listItem}
+            keyExtractor={item => item.id}
+            keyboardShouldPersistTaps="always"
+            ListEmptyComponent={<Text style={styles.noResult}>No search result</Text>}
+          />
+        }
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight //pageBackground
+  },
+  searchView: {
+    marginTop: 16,
+    marginLeft: 16,
+    marginRight: 16,
+    marginBottom: 8,
+    paddingLeft: 16,
+    paddingRight: 16,
+    borderRadius: 60,
+    borderWidth: 1,
+    borderColor: '#E5E5E5',
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: '#F5F5F5'
+  },
+  searchInput: {
+    flex: 1,
+    color: textPrimary,
+    ...$padding(6, 8),
+    fontSize: 15,
+    marginLeft: 4,
+    lineHeight: 20
+  },
+  searchingView: {
+    padding: 0,
+    alignItems: 'center'
+  },
+  seachingIcon: {
+    width: 120,
+    height: 120
+  },
+  noResult: {
+    color: '#999',
+    fontSize: 14,
+    padding: 20,
+    textAlign: 'center',
+  },
+  listView: {
+    flex: 1
+  },
+  itemView: {
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee'
+  },
+  stationInfo: {
+    flex: 1,
+    padding: 16
+  },
+  nameView: {
+    paddingTop: 3,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  stationName: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  stationAddress: {
+    color: '#666',
+    fontSize: 14,
+    paddingBottom: 8
+  },
+  infoStatus: {
+    fontSize: 10,
+    paddingTop: 3,
+    paddingLeft: 8,
+    paddingRight: 8,
+    paddingBottom: 3,
+    borderRadius: 5,
+    marginLeft: 12,
+  },
+  selected: {
+    color: textPrimary,
+    backgroundColor: colorAccent
+  },
+  available: {
+    color: textLight,
+    backgroundColor: '#90DB0A'
+  },
+  unavailable: {
+    color: '#999',
+    fontSize: 10.5,
+    paddingTop: 7,
+    paddingLeft: 9,
+    paddingRight: 9,
+    paddingBottom: 7,
+    backgroundColor: '#CCC'
+  },
+  connectView: {
+    paddingTop: 4,
+    paddingBottom: 4,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+
+  
+  directView: {
+    zIndex: 1,
+    paddingTop: 4,
+    paddingRight: 16,
+    alignItems: 'center'
+  },
+  distanceText: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 2
+  },
+});

+ 59 - 19
Strides-APP/app/pages/sign/Login.js

@@ -3,7 +3,7 @@
  * @邠心vbe on 2021/03/18
  */
 import React from 'react';
-import {View, Text, StyleSheet, Image, TextInput, ScrollView, Platform} from 'react-native';
+import {View, Text, StyleSheet, Image, TextInput, ScrollView, Platform, Pressable} from 'react-native';
 import CheckBox from '@react-native-community/checkbox';
 import { BackButton } from '../../components/Toolbar';
 import apiUser from '../../api/apiUser';
@@ -149,24 +149,37 @@ export default class Login extends React.Component {
         <BackButton/>
       </View>
       <ScrollView
-        style={ui.container}
+        style={styles.container}
         keyboardShouldPersistTaps={'handled'}>
         <View style={styles.header}>
-          <Image style={styles.headerImg} source={require('../../images/login-head.png')} />
+          <Image
+            style={styles.headerImg}
+            resizeMode='contain'
+            source={require('../../images/app-logo.png')} />
         </View>
         <View style={styles.loginView}>
           <View style={ui.center}>
-            <Image style={styles.logoImg} source={require('../../images/app-logo.png')} />
+            {/* <Image 
+              style={styles.logoImg}
+              resizeMode='contain'
+              source={require('../../images/app-logo.png')} /> */}
+            {/* <Text style={styles.loginTitle}>Please Login</Text> */}
           </View>
           <View style={styles.loginForm}>
             <View style={styles.inputView}>
-              <Zocial name='email' size={28} color='#999999' />
-              <TextInput 
+              {/* <Zocial name='email' size={28} color='#999999' /> */}
+              <Image 
+                style={styles.inputIcon}
+                source={require('../../images/user/sign-email.png')}
+              />
+              <TextInput
                 style={styles.inputText}
                 placeholder={"E-mail"}
+                keyboardType="email-address"
                 textContentType='emailAddress'
                 defaultValue={this.state.email}
                 maxLength={50}
+                clearButtonMode='while-editing'
                 onChangeText={(v) => {
                   this.setState({
                     email: v
@@ -174,7 +187,11 @@ export default class Login extends React.Component {
                 }}/>
             </View>
             <View style={styles.inputView}>
-              <Fontisto name='locked' size={28} color='#999999' style={{marginLeft: 3, marginRight: 2}} />
+              {/* <Fontisto name='locked' size={28} color='#999999' style={{marginLeft: 3, marginRight: 2}} /> */}
+              <Image 
+                style={styles.inputIcon}
+                source={require('../../images/user/sign-password.png')}
+              />
               <TextInput 
                 style={styles.inputText}
                 placeholder="Password"
@@ -206,7 +223,7 @@ export default class Login extends React.Component {
             </View>
             <Button
               style={styles.loginButton}
-              text='SIGN IN'
+              text='LOGIN'
               elevation={1.5}
               onClick={() => {
                 this.onLogin()
@@ -215,13 +232,19 @@ export default class Login extends React.Component {
           </View>
         </View>
         <View style={styles.signView}>
-          <Text style={{color: '#333'}}>New User?</Text>
-          <Text
+          <Text style={{color: textPrimary}}>New User?</Text>
+          {/* <Text
             style={styles.linksText}
             onPress={() => {
               startPage(PageList.register);
             }}
-          >Click here to sign up</Text>
+          >Click here to sign up</Text> */}
+          <Text 
+            style={styles.linksText}
+            onPress={() => startPage(PageList.register)}>Register as Public User</Text>
+          {/* <Text 
+            style={styles.linksText}
+            onPress={() => startPage(PageList.register, {isFleetUser: true})}>Register as Fleet / PHV Driver</Text> */}
         </View>
       </ScrollView>
       </View>
@@ -230,6 +253,10 @@ export default class Login extends React.Component {
 };
 
 const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
   backBtn:{
     top: 4,
     left: 2,
@@ -242,7 +269,7 @@ const styles = StyleSheet.create({
     paddingLeft: 32,
     paddingRight: 32,
     alignItems: 'center',
-    backgroundColor: colorAccent
+    //backgroundColor: colorPrimary
   },
   headerImg: {
     width: $vw(65),
@@ -253,7 +280,7 @@ const styles = StyleSheet.create({
     marginTop: -24,
     borderTopLeftRadius: 24,
     borderTopRightRadius: 24,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   logoImg: {
     width:136.19,
@@ -264,20 +291,31 @@ const styles = StyleSheet.create({
     paddingLeft: 16,
     paddingRight: 16,
   },
+  loginTitle: {
+    fontSize: 22,
+    fontWeight: 'bold',
+    color: textPrimary
+  },
+  inputIcon: {
+    width: 24,
+    height: 24
+  },
   inputView: {
     marginTop: 30,
-    borderRadius: 8,
+    borderRadius: 0,
     paddingTop: 6,
     paddingLeft: 16,
     paddingRight: 16,
     paddingBottom: 6,
     alignItems: 'center',
     flexDirection: 'row',
-    backgroundColor: '#F5F5F5'
+    borderBottomWidth: 1,
+    borderBottomColor: '#F5F5F5'
+    //backgroundColor: '#F5F5F5'
   },
   inputText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 15,
     paddingTop: 6,
     paddingLeft: 16,
@@ -285,7 +323,7 @@ const styles = StyleSheet.create({
   },
   loginButton: {
     marginTop: 32,
-    borderRadius: 4
+    borderRadius: 40
   },
   signView: {
     paddingTop: 32,
@@ -293,14 +331,16 @@ const styles = StyleSheet.create({
     alignItems: 'center',
   },
   checkText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingLeft: 8
   },
   linksText: {
     ...ui.link,
-    padding: 8,
     fontSize: 14,
+    padding: 4,
+    marginTop: 5,
+    fontWeight: 'bold',
     textDecorationLine: 'underline'
   }
 });

+ 305 - 301
Strides-APP/app/pages/sign/LoginV2.js

@@ -2,304 +2,308 @@
  * 登录页面V2
  * @邠心vbe on 2022/12/23
  */
- import React from 'react';
- import {View, Text, StyleSheet, Image, TextInput, ScrollView, Platform} from 'react-native';
- import { BackButton } from '../../components/Toolbar';
- import apiUser from '../../api/apiUser';
- import { setAccessToken } from '../../api/http';
- import { getStorageSync, setStorage, getStorageJsonSync, setStorageJson } from '../../utils/storage';
- import Button from '../../components/Button';
- import Dialog from '../../components/Dialog';
- import { PageList } from '../Router';
- import utils from '../../utils/utils';
- import CheckBoxText from '../../components/CheckBoxText';
- 
- export const AutoLogin = async (back) => {
-   const data = await getStorageJsonSync('loginData')
-   if (data && data.email && data.password) {
-     apiUser.login(data).then(res => {
-       if (res.data.accessToken) {
-         setAccessToken(res.data.accessToken);
-         if (back) back();
-       }
-     }).catch((err) => {
-       console.warn('AutoLogin', err);
-       toastShort('Sign in failed')
-     });
-   }
- }
- 
- //注册FCM通知token
- export const RegisterToken = async () => {
-   if (getUserId() > 0 && notifyToken.token) {
-     const thisDate = utils.formatYYMM(new Date()) + "-" + getUserId();
-     const lastDate = await getStorageSync('RegisterTokenDate');
-     console.log('>>>RegisterToken<<<', thisDate, lastDate + "●");
-     if (thisDate != lastDate) {
-       const params = {
-         os: notifyToken.os ? notifyToken.os : Platform.OS,
-         googleToken: notifyToken.token
-       }
-       apiUser.setNotifyToken(params).then(res => {
-         console.log('>>>RegisterToken-Suc<<<', res);
-         setStorage('RegisterTokenDate', thisDate);
-       }).catch(err => {
-         console.log('>>>RegisterToken-Err<<<', err);
-       });
-     }
-   }
- }
- 
- export default class Login extends React.Component {
- 
-   constructor(props) {
-     super(props);
-     this.state = {
-       email: '',
-       password: '',
-       rememberMe: true
-     }
-   }
- 
-   componentDidMount() {
-     /*if (this.props.route.params.action) {
-       this.props.navigation.addListener('beforeRemove', (e) => {
-         if (this.props.route.params.action == '401') {
-           //dispatchPage(StackActions.push('home'));
-           dispatchPage(state => {
-             console.log('routes', state);
-             const r = [];
-             var index = 0;
-             var homekey = '';
-             for (let i = 0; i < state.routes.length; i++) {
-               const item = state.routes[i];
-               if (item.name == 'home') {
-                 r.push(item);
-                 index = i;
-                 homekey = item.key;
-                 break;
-               } else {
-                 r.push(item);
-               }
-             }
-             console.log('new routes', r);
-             return {
-               ...CommonActions.goBack(),
-               source: this.props.route.key,
-               target: homekey,
-             };
-           });
-         }
-       });
-     }*/
-     this.getEmail();
-   }
- 
-   async getEmail() {
-     const data = await getStorageJsonSync('loginData');
-     if (data && data.email) {
-       this.setState({
-         email: data.email
-       });
-     }
-   }
- 
-   onLogin() {
-     //console.log(this.state);
-     if (this.state.email == '') {
-       toastShort('Please enter email address');
-       return;
-     }
-     if (this.state.password == '') {
-       toastShort('Please enter password');
-       return;
-     }
-     Dialog.showProgressDialog();
-     apiUser.login(this.state).then(res => {
-       console.log('res.data', res);
-       if (res.data.accessToken) {
-         setAccessToken(res.data.accessToken);
-         Dialog.dismissLoading();
-         if (this.state.rememberMe) {
-           setStorageJson('loginData', this.state);
-         } else {
-           setStorageJson('loginData', {});
-         }
-         startPage(PageList.home);
-         //this.props.navigation.goBack();
-       } else {
-         toastShort(res.msg);
-         Dialog.dismissLoading();
-       }
-     }).catch((err) => {
-       toastShort(err);
-       Dialog.dismissLoading();
-     });
-   }
- 
-   getBackTopPosition() {
-     return isIOS ? statusHeight : 4;
-   }
- 
-   render() {
-     return (
-       <View style={ui.flex1}>
-       <View style={[styles.backBtn, {top: this.getBackTopPosition()}]}>
-         <BackButton/>
-       </View>
-       <ScrollView
-         style={ui.container}
-         keyboardShouldPersistTaps={'handled'}>
-         <View style={styles.header}>
-           <Image
-            style={styles.headerImg}
-            resizeMode='contain'
-            source={require('../../images/app-logo.png')} />
-         </View>
-         <View style={styles.loginView}>
-           <View style={styles.loginForm}>
-             <View style={styles.inputView}>
-               <Zocial name='email' size={28} color='#999999' />
-               <TextInput 
-                 style={styles.inputText}
-                 placeholder={"E-mail"}
-                 textContentType='emailAddress'
-                 defaultValue={this.state.email}
-                 maxLength={50}
-                 onChangeText={(v) => {
-                   this.setState({
-                     email: v
-                   })
-                 }}/>
-             </View>
-             <View style={styles.inputView}>
-               <Fontisto name='locked' size={28} color='#999999' style={{marginLeft: 3, marginRight: 2}} />
-               <TextInput 
-                 style={styles.inputText}
-                 placeholder="Password"
-                 textContentType='password'
-                 secureTextEntry={true}
-                 maxLength={20}
-                 onChangeText={(v) => {
-                   this.setState({
-                     password: v
-                   })
-                 }}
-                 onSubmitEditing={() => {
-                   this.onLogin();
-                 }}/>
-             </View>
-             <View style={ui.flexcw}>
-               <View style={$padding(12, 8)}>
-                 <CheckBoxText
-                   value={this.state.rememberMe}
-                   onValueChange={(newValue) => {
-                     this.setState({ rememberMe: newValue });
-                   }}
-                   text={'Remember me'}
-                 />
-               </View>
-               <Text
-                 style={styles.linksText}
-                 onPress={() => startPage(PageList.forgotPassword)}>Forgot Password</Text>
-             </View>
-             <Button
-               style={styles.loginButton}
-               text='SIGN IN'
-               elevation={1.5}
-               onClick={() => {
-                 this.onLogin()
-               }}
-             />
-           </View>
-         </View>
-         <View style={styles.signView}>
-           <Text style={{color: '#333'}}>New User?</Text>
-           <Text
-             style={styles.linksText}
-             onPress={() => {
-               startPage(PageList.register);
-             }}
-           >Click here to sign up</Text>
-         </View>
-       </ScrollView>
-       </View>
-     );
-   }
- };
- 
- const styles = StyleSheet.create({
-   backBtn:{
-     top: 4,
-     left: 2,
-     zIndex: 1,
-     position: 'absolute'
-   },
-   header: {
-     height: 260,
-     paddingTop: 56,
-     paddingBottom: 20,
-     paddingLeft: 32,
-     paddingRight: 32,
-     alignItems: 'center'
-   },
-   headerImg: {
-     width: $vw(63),
-     height: 100
-   },
-   loginView: {
-     padding: 16,
-     marginTop: -24,
-     borderTopLeftRadius: 24,
-     borderTopRightRadius: 24,
-     backgroundColor: 'white'
-   },
-   logoImg: {
-     width:136.19,
-     height: 42.85,
-     marginTop: 18
-   },
-   loginForm: {
-     paddingLeft: 16,
-     paddingRight: 16,
-   },
-   inputView: {
-     marginTop: 30,
-     borderRadius: 8,
-     paddingTop: 6,
-     paddingLeft: 16,
-     paddingRight: 16,
-     paddingBottom: 6,
-     alignItems: 'center',
-     flexDirection: 'row',
-     backgroundColor: '#F5F5F5'
-   },
-   inputText: {
-     flex: 1,
-     color: '#333',
-     fontSize: 15,
-     paddingTop: 6,
-     paddingLeft: 16,
-     paddingBottom: 6
-   },
-   loginButton: {
-     marginTop: 32,
-     borderRadius: 4
-   },
-   signView: {
-     paddingTop: 32,
-     paddingBottom: 16,
-     alignItems: 'center',
-   },
-   checkText: {
-     color: '#333',
-     fontSize: 14,
-     paddingLeft: 8
-   },
-   linksText: {
-     ...ui.link,
-     padding: 8,
-     fontSize: 14,
-     textDecorationLine: 'underline'
-   }
- });
+import React from 'react';
+import {View, Text, StyleSheet, Image, TextInput, ScrollView, Platform} from 'react-native';
+import { BackButton } from '../../components/Toolbar';
+import apiUser from '../../api/apiUser';
+import { setAccessToken } from '../../api/http';
+import { getStorageSync, setStorage, getStorageJsonSync, setStorageJson } from '../../utils/storage';
+import Button from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import { PageList } from '../Router';
+import utils from '../../utils/utils';
+import CheckBoxText from '../../components/CheckBoxText';
+
+export const AutoLogin = async (back) => {
+  const data = await getStorageJsonSync('loginData')
+  if (data && data.email && data.password) {
+    apiUser.login(data).then(res => {
+      if (res.data.accessToken) {
+        setAccessToken(res.data.accessToken);
+        if (back) back();
+      }
+    }).catch((err) => {
+      console.warn('AutoLogin', err);
+      toastShort('Sign in failed')
+    });
+  }
+}
+
+//注册FCM通知token
+export const RegisterToken = async () => {
+  if (getUserId() > 0 && notifyToken.token) {
+    const thisDate = utils.formatYYMM(new Date()) + "-" + getUserId();
+    const lastDate = await getStorageSync('RegisterTokenDate');
+    console.log('>>>RegisterToken<<<', thisDate, lastDate + "●");
+    if (thisDate != lastDate) {
+      const params = {
+        os: notifyToken.os ? notifyToken.os : Platform.OS,
+        googleToken: notifyToken.token
+      }
+      apiUser.setNotifyToken(params).then(res => {
+        console.log('>>>RegisterToken-Suc<<<', res);
+        setStorage('RegisterTokenDate', thisDate);
+      }).catch(err => {
+        console.log('>>>RegisterToken-Err<<<', err);
+      });
+    }
+  }
+}
+
+export default class Login extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      email: '',
+      password: '',
+      rememberMe: true
+    }
+  }
+
+  componentDidMount() {
+    /*if (this.props.route.params.action) {
+      this.props.navigation.addListener('beforeRemove', (e) => {
+        if (this.props.route.params.action == '401') {
+          //dispatchPage(StackActions.push('home'));
+          dispatchPage(state => {
+            console.log('routes', state);
+            const r = [];
+            var index = 0;
+            var homekey = '';
+            for (let i = 0; i < state.routes.length; i++) {
+              const item = state.routes[i];
+              if (item.name == 'home') {
+                r.push(item);
+                index = i;
+                homekey = item.key;
+                break;
+              } else {
+                r.push(item);
+              }
+            }
+            console.log('new routes', r);
+            return {
+              ...CommonActions.goBack(),
+              source: this.props.route.key,
+              target: homekey,
+            };
+          });
+        }
+      });
+    }*/
+    this.getEmail();
+  }
+
+  async getEmail() {
+    const data = await getStorageJsonSync('loginData');
+    if (data && data.email) {
+      this.setState({
+        email: data.email
+      });
+    }
+  }
+
+  onLogin() {
+    //console.log(this.state);
+    if (this.state.email == '') {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (this.state.password == '') {
+      toastShort('Please enter password');
+      return;
+    }
+    Dialog.showProgressDialog();
+    apiUser.login(this.state).then(res => {
+      console.log('res.data', res);
+      if (res.data.accessToken) {
+        setAccessToken(res.data.accessToken);
+        Dialog.dismissLoading();
+        if (this.state.rememberMe) {
+          setStorageJson('loginData', this.state);
+        } else {
+          setStorageJson('loginData', {});
+        }
+        startPage(PageList.home);
+        //this.props.navigation.goBack();
+      } else {
+        toastShort(res.msg);
+        Dialog.dismissLoading();
+      }
+    }).catch((err) => {
+      toastShort(err);
+      Dialog.dismissLoading();
+    });
+  }
+
+  getBackTopPosition() {
+    return isIOS ? statusHeight : 4;
+  }
+
+  render() {
+    return (
+      <View style={ui.container}>
+      <View style={[styles.backBtn, {top: this.getBackTopPosition()}]}>
+        <BackButton/>
+      </View>
+      <ScrollView
+        style={styles.container}
+        keyboardShouldPersistTaps={'handled'}>
+        <View style={styles.header}>
+          <Image
+          style={styles.headerImg}
+          resizeMode='contain'
+          source={require('../../images/app-logo.png')} />
+        </View>
+        <View style={styles.loginView}>
+          <View style={styles.loginForm}>
+            <View style={styles.inputView}>
+              <Zocial name='email' size={28} color='#999999' />
+              <TextInput 
+                style={styles.inputText}
+                placeholder={"E-mail"}
+                textContentType='emailAddress'
+                defaultValue={this.state.email}
+                maxLength={50}
+                onChangeText={(v) => {
+                  this.setState({
+                    email: v
+                  })
+                }}/>
+            </View>
+            <View style={styles.inputView}>
+              <Fontisto name='locked' size={28} color='#999999' style={{marginLeft: 3, marginRight: 2}} />
+              <TextInput 
+                style={styles.inputText}
+                placeholder="Password"
+                textContentType='password'
+                secureTextEntry={true}
+                maxLength={20}
+                onChangeText={(v) => {
+                  this.setState({
+                    password: v
+                  })
+                }}
+                onSubmitEditing={() => {
+                  this.onLogin();
+                }}/>
+            </View>
+            <View style={ui.flexcw}>
+              <View style={$padding(12, 8)}>
+                <CheckBoxText
+                  value={this.state.rememberMe}
+                  onValueChange={(newValue) => {
+                    this.setState({ rememberMe: newValue });
+                  }}
+                  text={'Remember me'}
+                />
+              </View>
+              <Text
+                style={styles.linksText}
+                onPress={() => startPage(PageList.forgotPassword)}>Forgot Password</Text>
+            </View>
+            <Button
+              style={styles.loginButton}
+              text='SIGN IN'
+              elevation={1.5}
+              onClick={() => {
+                this.onLogin()
+              }}
+            />
+          </View>
+        </View>
+        <View style={styles.signView}>
+          <Text style={{color: '#333'}}>New User?</Text>
+          <Text
+            style={styles.linksText}
+            onPress={() => {
+              startPage(PageList.register);
+            }}
+          >Click here to sign up</Text>
+        </View>
+      </ScrollView>
+      </View>
+    );
+  }
+};
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  backBtn:{
+    top: 4,
+    left: 2,
+    zIndex: 1,
+    position: 'absolute'
+  },
+  header: {
+    height: 260,
+    paddingTop: 56,
+    paddingBottom: 20,
+    paddingLeft: 32,
+    paddingRight: 32,
+    alignItems: 'center'
+  },
+  headerImg: {
+    width: $vw(63),
+    height: 100
+  },
+  loginView: {
+    padding: 16,
+    marginTop: -24,
+    borderTopLeftRadius: 24,
+    borderTopRightRadius: 24,
+    backgroundColor: 'white'
+  },
+  logoImg: {
+    width:136.19,
+    height: 42.85,
+    marginTop: 18
+  },
+  loginForm: {
+    paddingLeft: 16,
+    paddingRight: 16,
+  },
+  inputView: {
+    marginTop: 30,
+    borderRadius: 8,
+    paddingTop: 6,
+    paddingLeft: 16,
+    paddingRight: 16,
+    paddingBottom: 6,
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: '#F5F5F5'
+  },
+  inputText: {
+    flex: 1,
+    color: '#333',
+    fontSize: 15,
+    paddingTop: 6,
+    paddingLeft: 16,
+    paddingBottom: 6
+  },
+  loginButton: {
+    marginTop: 32,
+    borderRadius: 4
+  },
+  signView: {
+    paddingTop: 32,
+    paddingBottom: 16,
+    alignItems: 'center',
+  },
+  checkText: {
+    color: '#333',
+    fontSize: 14,
+    paddingLeft: 8
+  },
+  linksText: {
+    ...ui.link,
+    padding: 8,
+    fontSize: 14,
+    textDecorationLine: 'underline'
+  }
+});

+ 35 - 77
Strides-APP/app/pages/sign/Register.js

@@ -18,6 +18,8 @@ import { host } from '../../api/http';
 import CheckBox from '@react-native-community/checkbox';
 import { ModalProps } from '../../components/BottomModal';
 import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
+import StrengthView from './StrengthView';
+import { UploadThemes } from '../../components/MyRefreshControl';
 
 const options = {
   width: 300,
@@ -29,30 +31,9 @@ const options = {
   compressImageQuality: 0.8,
   compressImageMaxWidth: 720,
   compressImageMaxHeight: 1280,
-  cropperStatusBarColor: colorAccent,
-  cropperToolbarColor: colorAccent,
-  cropperActiveWidgetColor: colorAccent
+  ...UploadThemes
 }
 
-export const StrengthView = ({strength}) => {
-  const getStyle = (num) => {
-    if (strength >= num) {
-      return {...styles.strengthItem, backgroundColor: colorAccent};
-    } else {
-      return styles.strengthItem;
-    }
-  }
-  return (
-    <>
-      <Text style={getStyle(1)}></Text>
-      <Text style={getStyle(2)}></Text>
-      <Text style={getStyle(3)}></Text>
-      {/* <Text style={getStyle(4)}></Text> */}
-      {/* <Text style={getStyle(5)}></Text> */}
-    </>
-  );
-};
-
 export default class Register extends React.Component {
   constructor(props) {
     super(props);
@@ -82,32 +63,18 @@ export default class Register extends React.Component {
   }
 
   applyStrength(text) {
-    var strength = 0;
-    if (text.length >= 8) {
-      strength += 1;
-    }
-    if (/\d{1,}/.test(text)) {
-      strength += 1;
-    }
-    if (/[A-z]{1,}/.test(text)) {
-      strength += 1;
-    }
-    /*if (/[A-Z]{1,}/.test(text)) {
-      strength += 1;
-    }
-    /*if (/\W{1,}/.test(text)) {
-      strength += 1;
-    }*/
-    if (this.state.strength != strength) {
-      this.setState({
-        password: text,
-        strength: strength
-      });
-    } else {
-      this.setState({
-        password: text
-      });
-    }
+    StrengthView.V1.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+    });
   }
 
   changeInfo(key, value) {
@@ -171,7 +138,7 @@ export default class Register extends React.Component {
       toastShort('Please enter password');
       return;
     }
-    if (this.state.strength < 3) {
+    if (this.state.strength < StrengthView.V1.maxStrength) {
       toastShort('Password is not strong');
       return;
     }
@@ -282,7 +249,7 @@ export default class Register extends React.Component {
             <View style={styles.logoView}>
               <Image
                 style={styles.logoImg}
-                source={require('../../images/tool-logo.png')}/>
+                source={require('../../images/app-logo.png')}/>
             </View>
           </View>
           <View style={{...styles.backView, top: this.getBackTopPosition()}}>
@@ -291,7 +258,7 @@ export default class Register extends React.Component {
               viewStyle={styles.backButtonView}
               onClick={() => goBack()}>
               <BackIcon/>
-              <Text style={{color: '#333',fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
+              <Text style={{color: textPrimary,fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
             </Button>
           </View>
           <View style={styles.signView}>
@@ -336,7 +303,7 @@ export default class Register extends React.Component {
                 <View style={styles.dropView}>
                   <TextInput style={styles.dropInput} editable={false}/>
                   <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
-                  <MaterialIcons name={'arrow-drop-down'} size={24} color={'#333'}/>
+                  <MaterialIcons name={'arrow-drop-down'} size={24} color={colorDark}/>
                   <Dropdown
                     style={styles.dropLayer}
                     title='Country'
@@ -383,16 +350,7 @@ export default class Register extends React.Component {
             <View style={styles.signInput}>
               <Text style={styles.inputLabel}></Text>
               <View style={styles.passwordView}>
-                <View style={styles.strengthView}>
-                  <Text style={{fontSize:12, paddingRight: 4, color: '#333'}}>Password Strength</Text>
-                  <StrengthView {...this.state}/>
-                </View>
-                <View>
-                  <Text style={styles.passwordRole}>Your Password Must Have:</Text>
-                  <Text style={styles.passwordRole}>- 8 or more characters</Text>
-                  {/* <Text style={styles.passwordRole}>- upper and lower case letters</Text> */}
-                  <Text style={styles.passwordRole}>- at least one number</Text>
-                </View>
+                <StrengthView.V1.VIEW {...this.state}/>
               </View>
             </View>
             <View style={styles.signInput}>
@@ -453,7 +411,7 @@ export default class Register extends React.Component {
                 <Text>Credit as registration bonus!</Text>
               </View>
               <View style={styles.codeView}>
-                <Text style={{ color: '#333', fontSize: 16 }}>Referral Code</Text>
+                <Text style={{ color: textPrimary, fontSize: 16 }}>Referral Code</Text>
                 <TextInput
                   style={styles.codeText}
                   maxLength={6}
@@ -536,7 +494,7 @@ const styles = StyleSheet.create({
   },
   backButton: {
     borderRadius: 60,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   backButtonView: {
     height: 43,
@@ -563,7 +521,7 @@ const styles = StyleSheet.create({
     marginTop: -30,
     borderTopLeftRadius: 20,
     borderTopRightRadius: 20,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   tabView: {
     marginBottom: 4,
@@ -576,7 +534,7 @@ const styles = StyleSheet.create({
   },
   tabText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 15,
     textAlign: 'center',
     ...$padding(10, 0),
@@ -586,7 +544,7 @@ const styles = StyleSheet.create({
     backgroundColor: colorAccent
   },
   title: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 18,
     fontWeight: '700',
     paddingTop: 4,
@@ -599,13 +557,13 @@ const styles = StyleSheet.create({
   },
   inputLabel: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     marginRight: 4
   },
   inputView: {
     flex: 2,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     borderRadius: 5,
     minHeight: 40,
@@ -632,11 +590,11 @@ const styles = StyleSheet.create({
   dropInput: {
     width: 12,
     padding: 6,
-    color: '#333',
+    color: textPrimary,
     minHeight: 40,
   },
   countryText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingRight: 4
   },
@@ -648,7 +606,7 @@ const styles = StyleSheet.create({
   },
   contactView: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 15,
     borderRadius: 4,
     minHeight: 40,
@@ -689,12 +647,12 @@ const styles = StyleSheet.create({
     backgroundColor: '#F5F5F5'
   },
   referTitle: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 18,
     fontWeight: 'bold'
   },
   referText: {
-    color: '#333',
+    color: textPrimary,
     paddingTop: 4,
     alignItems: 'flex-end',
     flexDirection: 'row'
@@ -712,7 +670,7 @@ const styles = StyleSheet.create({
     flexDirection: 'row'
   },
   codeText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 16,
     paddingTop: 6,
     paddingLeft: 12,
@@ -722,7 +680,7 @@ const styles = StyleSheet.create({
     marginLeft: 16,
     borderRadius: 6,
     textAlign: 'center',
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   signButton: {
     marginTop: 24,
@@ -757,7 +715,7 @@ const styles = StyleSheet.create({
     flexDirection: 'row'
   },
   agreeText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingTop: 2,
     paddingBottom: 2

+ 674 - 0
Strides-APP/app/pages/sign/RegisterDriver.js

@@ -0,0 +1,674 @@
+/**
+ * 司机用户注册页面
+ * @邠心vbe on 2023/02/01
+ */
+import React from 'react';
+import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import apiUser from '../../api/apiUser';
+import Button from '../../components/Button';
+import { PageList } from '../Router';
+import Dialog from '../../components/Dialog';
+import Modal from 'react-native-modal';
+import { RegisterDialog } from '../charge/InfoDialog';
+import Dropdown from '../../components/Dropdown';
+import ImagePicker from 'react-native-image-crop-picker';
+import apiUpload from '../../api/apiUpload';
+import { host } from '../../api/http';
+import { ModalProps } from '../../components/BottomModal';
+import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
+import StrengthView from './StrengthView';
+import CheckBox from '../../components/CheckBox';
+import { UploadThemes } from '../../components/ThemesConfig';
+
+const options = {
+  width: 300,
+  height: 200,
+  cropping: true,
+  multiple: false,
+  mediaType: 'photo',
+  writeTempFile: false,
+  compressImageQuality: 0.8,
+  compressImageMaxWidth: 720,
+  compressImageMaxHeight: 1280,
+  ...UploadThemes
+}
+
+export default class RegisterDriver extends React.Component {
+  constructor(props) {
+    super(props);
+    this.StrengthView = StrengthView.V1
+    this.state = {
+      agree: true,
+      strength: 0,
+      countryNum: '65',
+      countryShow: false,
+      userInfo: {},
+      countryNums: [],
+      wrongCount: 0,
+      params: {...this.props.route.params},
+      email: '',
+      password: '',
+      visible: false,
+      isFleetDriver: true,
+      pdvImages: ['', ''],
+      companyList: [],
+      fleetCompanyId: '',
+    };
+  }
+
+  componentDidMount() {
+    //console.log(this.state.params);
+    this.getCountryList();
+    this.getCompanyList();
+  }
+
+  applyStrength(text) {
+    this.StrengthView.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+    });
+  }
+
+  changeInfo(key, value) {
+    var info = this.state.userInfo;
+    info[key] = value;
+    this.setState({
+      'userInfo': info
+    });
+  }
+
+  getCountryList() {
+    GetCountryList(list => {
+      this.setState({
+        countryNums: list
+      })
+    })
+  }
+
+  getCompanyList() {
+    apiUser.getConmpany().then(res => {
+      if (res.data) {
+        this.setState({
+          companyList: res.data
+        })
+      }
+    }).catch(err => [
+      toastShort(err)
+    ])
+  }
+
+  onRegister() {
+    //console.log('sign up', this.state);
+    var info = this.state.userInfo;
+    /*if (!info.nickName) {
+      toastShort('Please enter display name');
+      return;
+    }
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    if (!info.phone) {
+      toastShort('Please enter contact number');
+      return;
+    }
+    if (!/^\d{6,15}$/.test(info.phone)) {
+      toastShort('Phone Number is incorrect format');
+      return;
+    }
+    if (!this.state.password) {
+      toastShort('Please enter password');
+      return;
+    }
+    if (this.state.strength < this.StrengthView.maxStrength) {
+      toastShort('Password is not strong');
+      return;
+    }
+    if (!info.password) {
+      toastShort('Please enter confirm password');
+      return;
+    }
+    if (info.password != this.state.password) {
+      toastShort('The twice passwords are inconsistent');
+      if (this.state.wrongCount < 3) {
+        this.setState({
+          wrongCount: this.state.wrongCount + 1
+        })
+      }
+      return;
+    }*/
+    if (this.state.isFleetDriver) {
+      if (!info.pdvLicence) {
+        toastShort('Please enter PDV Licence');
+        return;
+      }
+      if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
+        toastShort('Please upload PDV Licence Photos');
+        return;
+      }
+    }
+    let param = Object.assign({}, info);
+    //param.phone = this.state.countryNum + info.phone
+    //param.callingCode = this.state.countryNum;
+    if (this.state.isFleetDriver) {
+      //param.userType = 'Driver';
+      param.pdvLicencePictures = this.state.pdvImages;
+      param.fleetCompanyId = this.state.fleetCompanyId;
+    } else {
+      param.userType = 'Public';
+    }
+    console.log('params', param);
+    Dialog.showProgressDialog();
+    apiUser.registerDriver(param).then(res => {
+      Dialog.dismissLoading();
+      toastLong(res.msg ?? 'Submit successfully!');
+      this.backToLogin();
+    }).catch(err => {
+      toastLong(err);
+      Dialog.dismissLoading();
+    });
+  }
+  
+  getBackTopPosition() {
+    return isIOS ? statusHeight - 16 : 8;
+  }
+  
+  backToLogin() {
+    if (this.state.params.actionLogin) {
+      goBack()
+      //startPage(PageList.login);
+    } else {
+      goBack();
+    }
+  }
+
+  uploadImage(index) {
+    ImagePicker.openPicker(options).then(image => {
+      if (image.path) {
+        apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
+          if (res.success && res.data.picturePath) {
+            let imageUrl = this.state.pdvImages;
+            imageUrl[index] = res.data.picturePath;
+            this.setState({
+              pdvImages: imageUrl
+            });
+          } else {
+            toastShort('Upload failed, please retry');
+          }
+        }).catch(err => {
+          toastShort(err);
+        });
+      }
+    }).catch(err => {
+      //console.log(err);
+    });
+  }
+
+  changeAgree(ag) {
+    this.setState({
+      agree: ag
+    })
+  }
+
+  hideDialog() {
+    this.setState({
+      visible: false
+    });
+    this.backToLogin();
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <ScrollView
+          style={styles.scollView}
+          keyboardShouldPersistTaps={'handled'}>
+          <View style={styles.signView}>
+            {/* <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Display Name</Text>
+              <TextInput
+                style={styles.inputView} 
+                placeholder='Display Name'
+                maxLength={50}
+                onChangeText={v => this.changeInfo('nickName', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Email Address</Text>
+              <TextInput
+                style={styles.inputView}
+                placeholder='Email Address'
+                maxLength={50}
+                keyboardType="email-address"
+                textContentType='emailAddress'
+                onChangeText={v => this.changeInfo('email', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Phone Number</Text>
+              <View style={styles.mobileView}>
+                <View style={styles.dropView}>
+                  <TextInput style={styles.dropInput} editable={false}/>
+                  <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
+                  <MaterialIcons name={'arrow-drop-down'} size={24} color={colorDark}/>
+                  <Dropdown
+                    style={styles.dropLayer}
+                    title='Country'
+                    prefixText="+"
+                    list={this.state.countryNums}
+                    value={this.state.countryNum}
+                    nameKey='countryNum'
+                    valueKey='countryNum'
+                    onChange={(value, index)=> {
+                      this.setState({
+                        countryNum: value
+                      })
+                    }}
+                    customerItemView={
+                      (item, index, onClick) => 
+                      <CountryDropNum
+                        key={index} 
+                        country={item}
+                        value={this.state.countryNum}
+                        onClick={onClick}/>
+                    }/>
+                </View>
+                <TextInput
+                  style={styles.contactView}
+                  placeholder='Mobile Number'
+                  keyboardType='phone-pad'
+                  maxLength={15}
+                  onChangeText={v => this.changeInfo('phone', v)}
+                />
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Create Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={(value) => {
+                  this.applyStrength(value);
+                }}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}></Text>
+              <View style={styles.passwordView}>
+                <this.StrengthView.VIEW strength={this.state.strength}/>
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Confirm Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={v => this.changeInfo('password', v)}
+              />
+            </View> */}
+            { this.state.isFleetDriver &&
+              <>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>Your Company</Text>
+                <Dropdown
+                  style={[styles.inputView, ui.flexc]}
+                  title='Company'
+                  list={this.state.companyList}
+                  value={this.state.fleetCompanyId}
+                  valueKey='fleetCompanyId'
+                  nameKey='fleetCompanyName'
+                  onChange={(value, index)=> {
+                    this.setState({
+                      fleetCompanyId: value
+                    })
+                  }}/>
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
+                <TextInput
+                  style={styles.inputView}
+                  placeholder='PH Driver Vocational Licence'
+                  maxLength={20}
+                  onChangeText={v => this.changeInfo('pdvLicence', v)}
+                />
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'  PDV Photos\n(Front & Back)'}</Text>
+                <View style={styles.uploadGroup}>
+                  { this.state.pdvImages.map((item, index) => (
+                    <UploadView
+                      key={index}
+                      onPress={() => this.uploadImage(index)}
+                      url={item}/>
+                    )) 
+                  }
+                </View>
+              </View>
+              </>
+            }
+            {/* <View style={styles.referView}>
+              <Text style={styles.referTitle}>Have a referral code?</Text>
+              <View style={styles.referText}>
+                <Text>You'll get</Text>
+                <Text style={styles.weight}>$5</Text>
+                <Text>Credit as registration bonus!</Text>
+              </View>
+              <View style={styles.codeView}>
+                <Text style={{ color: textPrimary, fontSize: 16 }}>Referral Code</Text>
+                <TextInput
+                  style={styles.codeText}
+                  maxLength={6}
+                  placeholder='Referral Code'
+                  onChangeText={v => this.changeInfo('referralCode', v)}
+                />
+              </View>
+            </View> */}
+            <View style={styles.agreeView}>
+              <CheckBox
+                value={this.state.agree}
+                onValueChange={v => this.changeAgree(v)}
+              />
+              <View style={styles.agreeTextRow}>
+                <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
+                  {'I agree that the information that i’m submitting is the latest and accurate.'}
+                </Text>
+                {/* <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
+                <Text style={styles.agreeText}>{' '}</Text>
+                <Text style={styles.agreeText}>{'and '}</Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
+                <Text style={styles.agreeText}>.</Text> */}
+              </View>
+            </View>
+            <Button
+              style={styles.signButton}
+              elevation={1.5}
+              disabled={!this.state.agree}
+              text='SUBMIT'
+              fontSize={14}
+              onClick={() => {
+                this.onRegister();
+              }}
+            />
+          </View>
+        </ScrollView>
+        <Modal
+          isVisible={this.state.visible}
+          onBackButtonPress={() => this.hideDialog()}
+          {...ModalProps}>
+          <RegisterDialog
+            address={this.state.email}
+            onClose={() => this.hideDialog()}
+          />
+        </Modal>
+      </View>
+    );
+  }
+}
+
+const UploadView = ({url, onPress}) => (
+  <Pressable
+    style={styles.uploadView}
+    onPress={onPress}>
+    { url == ''
+    ? <Image
+        style={styles.uploadIcon}
+        source={require('../../images/icon/ic-add-photo.png')}/>
+    : <Image
+        style={styles.uploadIcon}
+        defaultSource={require('../../images/icon/icon-upload-default.png')}
+        source={{uri: host + url}}/>
+    }
+  </Pressable>
+)
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  header: {
+    paddingTop: 56,
+    backgroundColor: colorAccent
+  },
+  backView: {
+    top: 12,
+    zIndex: 5,
+    padding: 16,
+    position: 'absolute',
+    flexDirection: 'row'
+  },
+  backButton: {
+    borderRadius: 60,
+    backgroundColor: colorLight
+  },
+  backButtonView: {
+    height: 43,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  scollView: {
+    flex: 1
+  },
+  logoView: {
+    paddingTop: 32,
+    paddingBottom: 56,
+    alignItems: 'center'
+  },
+  logoImg: {
+    width:165.09,
+    height: 51.94,
+  },
+  signView: {
+    flex: 1,
+    padding: 16,
+    //marginTop: -30,
+    borderTopLeftRadius: 20,
+    borderTopRightRadius: 20,
+    backgroundColor: colorLight
+  },
+  tabView: {
+    marginBottom: 4,
+    borderWidth: 1,
+    borderRadius: 6,
+    overflow: 'hidden',
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderColor: colorAccent,
+  },
+  tabText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    textAlign: 'center',
+    ...$padding(10, 0),
+  },
+  tabActive: {
+    fontWeight: 'bold',
+    backgroundColor: colorAccent
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: '700',
+    paddingTop: 4,
+    paddingBottom: 6,
+  },
+  signInput: {
+    marginTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  inputLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    marginRight: 4
+  },
+  inputView: {
+    flex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    borderRadius: 5,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    backgroundColor: '#F5F5F5'
+  },
+  mobileView: {
+    flex: 2,
+    marginLeft: -12,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  dropView: {
+    fontSize: 16,
+    borderRadius: 4,
+    paddingRight: 4,
+    flexDirection: 'row',
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  dropInput: {
+    width: 12,
+    padding: 6,
+    color: textPrimary,
+    minHeight: 40,
+  },
+  countryText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingRight: 4
+  },
+  dropLayer: {
+    left: 0,
+    right: 0,
+    opacity: 0,
+    position: 'absolute'
+  },
+  contactView: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    borderRadius: 4,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    marginLeft: 10,
+    backgroundColor: '#F5F5F5'
+  },
+  passwordView: {
+    flex: 2,
+    marginTop: -8,
+  },
+  referView: {
+    padding: 16,
+    marginTop: 24,
+    borderRadius: 6,
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  referTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  referText: {
+    color: textPrimary,
+    paddingTop: 4,
+    alignItems: 'flex-end',
+    flexDirection: 'row'
+  },
+  weight: {
+    fontSize: 18,
+    paddingLeft: 4,
+    paddingRight: 4,
+    color: colorAccent,
+    fontWeight: 'bold'
+  },
+  codeView: {
+    paddingTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  codeText: {
+    color: textPrimary,
+    fontSize: 16,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    minHeight: 40,
+    marginLeft: 16,
+    borderRadius: 6,
+    textAlign: 'center',
+    backgroundColor: colorLight
+  },
+  signButton: {
+    marginTop: 24,
+    marginBottom: 8,
+  },
+  uploadGroup: {
+    flex: 2,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center'
+  },
+  uploadView: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  uploadIcon: {
+    width: $vw(28),
+    height: $vw(28),
+    borderRadius: 6
+  },
+  agreeView: {
+    marginTop: 24,
+    marginBottom: 16,
+    flexDirection: 'row',
+    alignItems: 'flex-start',
+  },
+  agreeTextRow: {
+    flex: 1,
+    paddingTop: 4,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    flexDirection: 'row'
+  },
+  agreeText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2
+  },
+  agreeLink: {
+    ...ui.link,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2,
+    textDecorationLine: 'underline'
+  }
+});
+ 

+ 693 - 0
Strides-APP/app/pages/sign/RegisterPublic.js

@@ -0,0 +1,693 @@
+/**
+ * 司机用户注册页面
+ * @邠心vbe on 2023/02/01
+ */
+import React from 'react';
+import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import apiUser from '../../api/apiUser';
+import Button from '../../components/Button';
+import { PageList } from '../Router';
+import Dialog from '../../components/Dialog';
+import Modal from 'react-native-modal';
+import { RegisterDialog } from '../charge/InfoDialog';
+import Dropdown from '../../components/Dropdown';
+import ImagePicker from 'react-native-image-crop-picker';
+import apiUpload from '../../api/apiUpload';
+import { host } from '../../api/http';
+import { ModalProps } from '../../components/BottomModal';
+import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
+import StrengthView from './StrengthView';
+import CheckBox from '../../components/CheckBox';
+import { UploadThemes } from '../../components/ThemesConfig';
+ 
+const options = {
+  width: 300,
+  height: 200,
+  cropping: true,
+  multiple: false,
+  mediaType: 'photo',
+  writeTempFile: false,
+  compressImageQuality: 0.8,
+  compressImageMaxWidth: 720,
+  compressImageMaxHeight: 1280,
+  ...UploadThemes
+}
+
+export default class RegisterPublic extends React.Component {
+  constructor(props) {
+    super(props);
+    this.StrengthView = StrengthView.V1
+    this.state = {
+      agree: true,
+      strength: 0,
+      countryNum: '65',
+      countryShow: false,
+      userInfo: {},
+      countryNums: [],
+      wrongCount: 0,
+      params: {...this.props.route.params},
+      email: '',
+      password: '',
+      fleetCompanyId: '',
+      visible: false,
+      isFleetDriver: false,
+      pdvImages: ['', ''],
+      companyList: []
+    };
+  }
+
+  componentDidMount() {
+    //console.log(this.state.params);
+    this.getCountryList();
+    this.getCompanyList();
+  }
+
+  applyStrength(text) {
+    this.StrengthView.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+    });
+  }
+
+  changeInfo(key, value) {
+    var info = this.state.userInfo;
+    info[key] = value;
+    this.setState({
+      'userInfo': info
+    });
+  }
+
+  getCountryList() {
+    GetCountryList(list => {
+      this.setState({
+        countryNums: list
+      })
+    })
+  }
+
+  getCompanyList() {
+    apiUser.getConmpany().then(res => {
+      if (res.data) {
+        this.setState({
+          companyList: res.data
+        })
+      }
+    }).catch(err => [
+      toastShort(err)
+    ])
+  }
+
+  onRegister() {
+    //console.log('sign up', this.state);
+    var info = this.state.userInfo;
+    if (!info.nickName) {
+      toastShort('Please enter display name');
+      return;
+    }
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    if (!info.phone) {
+      toastShort('Please enter contact number');
+      return;
+    }
+    if (!/^\d{6,15}$/.test(info.phone)) {
+      toastShort('Phone Number is incorrect format');
+      return;
+    }
+    if (!this.state.password) {
+      toastShort('Please enter password');
+      return;
+    }
+    if (this.state.strength < this.StrengthView.maxStrength) {
+      toastShort('Password is not strong');
+      return;
+    }
+    if (!info.password) {
+      toastShort('Please enter confirm password');
+      return;
+    }
+    if (info.password != this.state.password) {
+      toastShort('The twice passwords are inconsistent');
+      if (this.state.wrongCount < 3) {
+        this.setState({
+          wrongCount: this.state.wrongCount + 1
+        })
+      }
+      return;
+    }
+    if (this.state.isFleetDriver) {
+      if (!info.pdvLicence) {
+        toastShort('Please enter PDV Licence');
+        return;
+      }
+      if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
+        toastShort('Please upload PDV Licence Photos');
+        return;
+      }
+    }
+    let param = Object.assign({}, info);
+    //param.phone = this.state.countryNum + info.phone
+    param.callingCode = this.state.countryNum;
+    if (this.state.isFleetDriver) {
+      param.userType = 'Driver';
+      param.pdvLicencePictures = this.state.pdvImages;
+      param.fleetCompanyId = this.state.fleetCompanyId;
+    } else {
+      param.userType = 'Public';
+    }
+    console.log('params', param);
+    Dialog.showProgressDialog();
+    apiUser.register(param).then(res => {
+      Dialog.dismissLoading();
+      //toastShort('Sign up successfully!');
+      this.setState({
+        email: param.email,
+        visible: true
+      });
+      //this.backToLogin();
+    }).catch(err => {
+      toastShort(err);
+      Dialog.dismissLoading();
+    });
+  }
+  
+  getBackTopPosition() {
+    return isIOS ? statusHeight - 16 : 8;
+  }
+  
+  backToLogin() {
+    if (this.state.params.actionLogin) {
+      goBack()
+      startPage(PageList.login);
+    } else {
+      goBack();
+    }
+  }
+
+  uploadImage(index) {
+    ImagePicker.openPicker(options).then(image => {
+      if (image.path) {
+        apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
+          if (res.success && res.data.picturePath) {
+            let imageUrl = this.state.pdvImages;
+            imageUrl[index] = res.data.picturePath;
+            this.setState({
+              pdvImages: imageUrl
+            });
+          } else {
+            toastShort('Upload failed, please retry');
+          }
+        }).catch(err => {
+          toastShort(err);
+        });
+      }
+    }).catch(err => {
+      //console.log(err);
+    });
+  }
+
+  changeAgree(ag) {
+    this.setState({
+      agree: ag
+    })
+  }
+
+  hideDialog() {
+    this.setState({
+      visible: false
+    });
+    this.backToLogin();
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <ScrollView
+          style={styles.scollView}
+          keyboardShouldPersistTaps={'handled'}>
+          <View style={styles.signView}>
+            {/* <View style={styles.tabView}>
+              <Text 
+                style={[
+                  styles.tabText, 
+                  this.state.isFleetDriver ? {} : styles.tabActive
+                ]}
+                onPress={() => this.changeTab(false)}
+              >Public Users</Text>
+              <Text
+                style={[
+                  styles.tabText,
+                  this.state.isFleetDriver ? styles.tabActive: {}
+                ]}
+                onPress={() => this.changeTab(true)}
+              >Fleet/PH Drivers</Text>
+            </View> */}
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Display Name</Text>
+              <TextInput
+                style={styles.inputView} 
+                placeholder='Display Name'
+                maxLength={50}
+                onChangeText={v => this.changeInfo('nickName', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Email Address</Text>
+              <TextInput
+                style={styles.inputView}
+                placeholder='Email Address'
+                maxLength={50}
+                keyboardType="email-address"
+                textContentType='emailAddress'
+                onChangeText={v => this.changeInfo('email', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Phone Number</Text>
+              <View style={styles.mobileView}>
+                <View style={styles.dropView}>
+                  <TextInput style={styles.dropInput} editable={false}/>
+                  <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
+                  <MaterialIcons name={'arrow-drop-down'} size={24} color={colorDark}/>
+                  <Dropdown
+                    style={styles.dropLayer}
+                    title='Country'
+                    prefixText="+"
+                    list={this.state.countryNums}
+                    value={this.state.countryNum}
+                    nameKey='countryNum'
+                    valueKey='countryNum'
+                    onChange={(value, index)=> {
+                      this.setState({
+                        countryNum: value
+                      })
+                    }}
+                    customerItemView={
+                      (item, index, onClick) => 
+                      <CountryDropNum
+                        key={index} 
+                        country={item}
+                        value={this.state.countryNum}
+                        onClick={onClick}/>
+                    }/>
+                </View>
+                <TextInput
+                  style={styles.contactView}
+                  placeholder='Mobile Number'
+                  keyboardType='phone-pad'
+                  maxLength={15}
+                  onChangeText={v => this.changeInfo('phone', v)}
+                />
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Create Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={(value) => {
+                  this.applyStrength(value);
+                }}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}></Text>
+              <View style={styles.passwordView}>
+                <this.StrengthView.VIEW strength={this.state.strength}/>
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Confirm Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={v => this.changeInfo('password', v)}
+              />
+            </View>
+            { this.state.isFleetDriver &&
+              <>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>Your Company</Text>
+                <Dropdown
+                  style={[styles.inputView, ui.flexc]}
+                  title='Company'
+                  list={this.state.companyList}
+                  value={this.state.fleetCompanyId}
+                  valueKey='fleetCompanyId'
+                  nameKey='fleetCompanyName'
+                  onChange={(value, index)=> {
+                    this.setState({
+                      fleetCompanyId: value
+                    })
+                  }}/>
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
+                <TextInput
+                  style={styles.inputView}
+                  placeholder='PH Driver Vocational Licence'
+                  maxLength={20}
+                  onChangeText={v => this.changeInfo('pdvLicence', v)}
+                />
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'  PDV Photos\n(Front & Back)'}</Text>
+                <View style={styles.uploadGroup}>
+                  { this.state.pdvImages.map((item, index) => (
+                    <UploadView
+                      key={index}
+                      onPress={() => this.uploadImage(index)}
+                      url={item}/>
+                    )) 
+                  }
+                </View>
+              </View>
+              </>
+            }
+            {/* <View style={styles.referView}>
+              <Text style={styles.referTitle}>Have a referral code?</Text>
+              <View style={styles.referText}>
+                <Text>You'll get</Text>
+                <Text style={styles.weight}>$5</Text>
+                <Text>Credit as registration bonus!</Text>
+              </View>
+              <View style={styles.codeView}>
+                <Text style={{ color: textPrimary, fontSize: 16 }}>Referral Code</Text>
+                <TextInput
+                  style={styles.codeText}
+                  maxLength={6}
+                  placeholder='Referral Code'
+                  onChangeText={v => this.changeInfo('referralCode', v)}
+                />
+              </View>
+            </View> */}
+            <View style={styles.agreeView}>
+              <CheckBox
+                value={this.state.agree}
+                onValueChange={v => this.changeAgree(v)}
+              />
+              <View style={styles.agreeTextRow}>
+                <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
+                  {'I have read and I agree with the '}
+                </Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
+                <Text style={styles.agreeText}>{' '}</Text>
+                <Text style={styles.agreeText}>{'and '}</Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
+                <Text style={styles.agreeText}>.</Text>
+              </View>
+            </View>
+            <Button
+              style={styles.signButton}
+              elevation={1.5}
+              disabled={!this.state.agree}
+              text='SIGN UP'
+              fontSize={14}
+              onClick={() => {
+                this.onRegister();
+              }}
+            />
+          </View>
+        </ScrollView>
+        <Modal
+          isVisible={this.state.visible}
+          onBackButtonPress={() => this.hideDialog()}
+          {...ModalProps}>
+          <RegisterDialog
+            address={this.state.email}
+            onClose={() => this.hideDialog()}
+          />
+        </Modal>
+      </View>
+    );
+  }
+}
+
+const UploadView = ({url, onPress}) => (
+  <Pressable
+    style={styles.uploadView}
+    onPress={onPress}>
+    { url == ''
+    ? <Image
+        style={styles.uploadIcon}
+        source={require('../../images/icon/ic-add-photo.png')}/>
+    : <Image
+        style={styles.uploadIcon}
+        defaultSource={require('../../images/icon/icon-upload-default.png')}
+        source={{uri: host + url}}/>
+    }
+  </Pressable>
+)
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  header: {
+    paddingTop: 56,
+    backgroundColor: colorAccent
+  },
+  backView: {
+    top: 12,
+    zIndex: 5,
+    padding: 16,
+    position: 'absolute',
+    flexDirection: 'row'
+  },
+  backButton: {
+    borderRadius: 60,
+    backgroundColor: colorLight
+  },
+  backButtonView: {
+    height: 43,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  scollView: {
+    flex: 1
+  },
+  logoView: {
+    paddingTop: 32,
+    paddingBottom: 56,
+    alignItems: 'center'
+  },
+  logoImg: {
+    width:165.09,
+    height: 51.94,
+  },
+  signView: {
+    flex: 1,
+    padding: 16,
+    //marginTop: -30,
+    borderTopLeftRadius: 20,
+    borderTopRightRadius: 20,
+    backgroundColor: colorLight
+  },
+  tabView: {
+    marginBottom: 4,
+    borderWidth: 1,
+    borderRadius: 6,
+    overflow: 'hidden',
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderColor: colorAccent,
+  },
+  tabText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    textAlign: 'center',
+    ...$padding(10, 0),
+  },
+  tabActive: {
+    fontWeight: 'bold',
+    backgroundColor: colorAccent
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: '700',
+    paddingTop: 4,
+    paddingBottom: 6,
+  },
+  signInput: {
+    marginTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  inputLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    marginRight: 4
+  },
+  inputView: {
+    flex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    borderRadius: 5,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    backgroundColor: '#F5F5F5'
+  },
+  mobileView: {
+    flex: 2,
+    marginLeft: -12,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  dropView: {
+    fontSize: 16,
+    borderRadius: 4,
+    paddingRight: 4,
+    flexDirection: 'row',
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  dropInput: {
+    width: 12,
+    padding: 6,
+    color: textPrimary,
+    minHeight: 40,
+  },
+  countryText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingRight: 4
+  },
+  dropLayer: {
+    left: 0,
+    right: 0,
+    opacity: 0,
+    position: 'absolute'
+  },
+  contactView: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    borderRadius: 4,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    marginLeft: 10,
+    backgroundColor: '#F5F5F5'
+  },
+  passwordView: {
+    flex: 2,
+    marginTop: -8,
+  },
+  referView: {
+    padding: 16,
+    marginTop: 24,
+    borderRadius: 6,
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  referTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  referText: {
+    color: textPrimary,
+    paddingTop: 4,
+    alignItems: 'flex-end',
+    flexDirection: 'row'
+  },
+  weight: {
+    fontSize: 18,
+    paddingLeft: 4,
+    paddingRight: 4,
+    color: colorAccent,
+    fontWeight: 'bold'
+  },
+  codeView: {
+    paddingTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  codeText: {
+    color: textPrimary,
+    fontSize: 16,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    minHeight: 40,
+    marginLeft: 16,
+    borderRadius: 6,
+    textAlign: 'center',
+    backgroundColor: colorLight
+  },
+  signButton: {
+    marginTop: 24,
+    marginBottom: 8,
+  },
+  uploadGroup: {
+    flex: 2,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center'
+  },
+  uploadView: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  uploadIcon: {
+    width: $vw(28),
+    height: $vw(28),
+    borderRadius: 6
+  },
+  agreeView: {
+    marginTop: 24,
+    marginBottom: 16,
+    flexDirection: 'row',
+    alignItems: 'flex-start',
+  },
+  agreeTextRow: {
+    flex: 1,
+    paddingTop: 4,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    flexDirection: 'row'
+  },
+  agreeText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2
+  },
+  agreeLink: {
+    ...ui.link,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2,
+    textDecorationLine: 'underline'
+  }
+});

+ 719 - 767
Strides-APP/app/pages/sign/RegisterV2.js

@@ -2,771 +2,723 @@
  * 注册页面V2
  * @邠心vbe on 2022/12/23
  */
- import React from 'react';
- import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
- import { BackIcon } from '../../components/Toolbar';
- import apiUser from '../../api/apiUser';
- import Button from '../../components/Button';
- import { PageList } from '../Router';
- import Dialog from '../../components/Dialog';
- import Modal from 'react-native-modal';
- import { RegisterDialog } from '../charge/InfoDialog';
- import Dropdown from '../../components/Dropdown';
- import ImagePicker from 'react-native-image-crop-picker';
- import apiUpload from '../../api/apiUpload';
- import { host } from '../../api/http';
- import { ModalProps } from '../../components/BottomModal';
- import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
+import React from 'react';
+import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import { BackButton, BackIcon, Styles } from '../../components/Toolbar';
+import apiUser from '../../api/apiUser';
+import Button from '../../components/Button';
+import { PageList } from '../Router';
+import Dialog from '../../components/Dialog';
+import Modal from 'react-native-modal';
+import { RegisterDialog } from '../charge/InfoDialog';
+import Dropdown from '../../components/Dropdown';
+import ImagePicker from 'react-native-image-crop-picker';
+import apiUpload from '../../api/apiUpload';
+import { host } from '../../api/http';
+import { ModalProps } from '../../components/BottomModal';
+import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import CheckBox from '../../components/CheckBox';
- 
- const options = {
-   width: 300,
-   height: 200,
-   cropping: true,
-   multiple: false,
-   mediaType: 'photo',
-   writeTempFile: false,
-   compressImageQuality: 0.8,
-   compressImageMaxWidth: 720,
-   compressImageMaxHeight: 1280,
-   cropperStatusBarColor: colorAccent,
-   cropperToolbarColor: colorAccent,
-   cropperActiveWidgetColor: colorAccent
- }
- 
- export const StrengthView = ({strength}) => {
-   const getStyle = (num) => {
-     if (strength >= num) {
-       return {...styles.strengthItem, backgroundColor: colorAccent};
-     } else {
-       return styles.strengthItem;
-     }
-   }
-   return (
-     <>
-       <Text style={getStyle(1)}></Text>
-       <Text style={getStyle(2)}></Text>
-       <Text style={getStyle(3)}></Text>
-       {/* <Text style={getStyle(4)}></Text> */}
-       {/* <Text style={getStyle(5)}></Text> */}
-     </>
-   );
- };
- 
- export default class Register extends React.Component {
-   constructor(props) {
-     super(props);
-     this.state = {
-       agree: true,
-       strength: 0,
-       countryNum: '65',
-       countryShow: false,
-       userInfo: {},
-       countryNums: [],
-       wrongCount: 0,
-       params: {...this.props.route.params},
-       email: '',
-       password: '',
-       fleetCompanyId: '',
-       visible: false,
-       isFleetDriver: false,
-       pdvImages: ['', ''],
-       companyList: []
-     };
-   }
- 
-   componentDidMount() {
-     //console.log(this.state.params);
-     this.getCountryList();
-     this.getCompanyList();
-   }
- 
-   applyStrength(text) {
-     var strength = 0;
-     if (text.length >= 8) {
-       strength += 1;
-     }
-     if (/\d{1,}/.test(text)) {
-       strength += 1;
-     }
-     if (/[A-z]{1,}/.test(text)) {
-       strength += 1;
-     }
-     /*if (/[A-Z]{1,}/.test(text)) {
-       strength += 1;
-     }
-     /*if (/\W{1,}/.test(text)) {
-       strength += 1;
-     }*/
-     if (this.state.strength != strength) {
-       this.setState({
-         password: text,
-         strength: strength
-       });
-     } else {
-       this.setState({
-         password: text
-       });
-     }
-   }
- 
-   changeInfo(key, value) {
-     var info = this.state.userInfo;
-     info[key] = value;
-     this.setState({
-       'userInfo': info
-     });
-   }
- 
-   changeTab(type) {
-     this.setState({
-       isFleetDriver: type
-     })
-   }
- 
-   getCountryList() {
-     GetCountryList(list => {
-       this.setState({
-         countryNums: list
-       })
-     })
-   }
- 
-   getCompanyList() {
-     apiUser.getConmpany().then(res => {
-       if (res.data) {
-         this.setState({
-           companyList: res.data
-         })
-       }
-     }).catch(err => [
-       toastShort(err)
-     ])
-   }
- 
-   onRegister() {
-     //console.log('sign up', this.state);
-     var info = this.state.userInfo;
-     if (!info.nickName) {
-       toastShort('Please enter display name');
-       return;
-     }
-     if (!info.email) {
-       toastShort('Please enter email address');
-       return;
-     }
-     if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
-       toastShort('Email is incorrect format');
-       return;
-     }
-     if (!info.phone) {
-       toastShort('Please enter contact number');
-       return;
-     }
-     if (!/^\d{6,15}$/.test(info.phone)) {
-       toastShort('Phone Number is incorrect format');
-       return;
-     }
-     if (!this.state.password) {
-       toastShort('Please enter password');
-       return;
-     }
-     if (this.state.strength < 3) {
-       toastShort('Password is not strong');
-       return;
-     }
-     if (!info.password) {
-       toastShort('Please enter confirm password');
-       return;
-     }
-     if (info.password != this.state.password) {
-       toastShort('The twice passwords are inconsistent');
-       if (this.state.wrongCount < 3) {
-         this.setState({
-           wrongCount: this.state.wrongCount + 1
-         })
-       }
-       return;
-     }
-     if (this.state.isFleetDriver) {
-       if (!info.pdvLicence) {
-         toastShort('Please enter PDV Licence');
-         return;
-       }
-       if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
-         toastShort('Please upload PDV Licence Photos');
-         return;
-       }
-     }
-     let param = Object.assign({}, info);
-     //param.phone = this.state.countryNum + info.phone
-     param.callingCode = this.state.countryNum;
-     if (this.state.isFleetDriver) {
-       param.userType = 'Driver';
-       param.pdvLicencePictures = this.state.pdvImages;
-       param.fleetCompanyId = this.state.fleetCompanyId;
-     } else {
-       param.userType = 'Public';
-     }
-     console.log('params', param);
-     Dialog.showProgressDialog();
-     apiUser.register(param).then(res => {
-       Dialog.dismissLoading();
-       //toastShort('Sign up successfully!');
-       this.setState({
-         email: param.email,
-         visible: true
-       });
-       //this.backToLogin();
-     }).catch(err => {
-       toastShort(err);
-       Dialog.dismissLoading();
-     });
-   }
-   
-   getBackTopPosition() {
-     return isIOS ? statusHeight - 16 : 8;
-   }
-   
-   backToLogin() {
-     if (this.state.params.actionLogin) {
-       goBack()
-       startPage(PageList.login);
-     } else {
-       goBack();
-     }
-   }
- 
-   uploadImage(index) {
-     ImagePicker.openPicker(options).then(image => {
-       if (image.path) {
-         apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
-           if (res.success && res.data.picturePath) {
-             let imageUrl = this.state.pdvImages;
-             imageUrl[index] = res.data.picturePath;
-             this.setState({
-               pdvImages: imageUrl
-             });
-           } else {
-             toastShort('Upload failed, please retry');
-           }
-         }).catch(err => {
-           toastShort(err);
-         });
-       }
-     }).catch(err => {
-       //console.log(err);
-     });
-   }
- 
-   changeAgree(ag) {
-     this.setState({
-       agree: ag
-     })
-   }
- 
-   hideDialog() {
-     this.setState({
-       visible: false
-     });
-     this.backToLogin();
-   }
- 
-   render() {
-     return (
-       <View style={StyleSheet.absoluteFillObject}>
-         <ScrollView
-           style={styles.scollView}
-           keyboardShouldPersistTaps={'handled'}>
-           <View style={styles.header}>
-             <View style={styles.logoView}>
-               {/* <Image
-                 style={styles.logoImg}
-                 source={require('../../images/tool-logo.png')}/> */}
-             </View>
-           </View>
-           <View style={{...styles.backView, top: this.getBackTopPosition()}}>
-             <Button
-               style={styles.backButton}
-               viewStyle={styles.backButtonView}
-               onClick={() => goBack()}>
-               <BackIcon/>
-               <Text style={{color: '#333',fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
-             </Button>
-           </View>
-           <View style={styles.signView}>
-             <View style={styles.tabView}>
-               <Text 
-                 style={[
-                   styles.tabText, 
-                   this.state.isFleetDriver ? {} : styles.tabActive
-                 ]}
-                 onPress={() => this.changeTab(false)}
-               >Public Users</Text>
-               <Text
-                 style={[
-                   styles.tabText,
-                   this.state.isFleetDriver ? styles.tabActive: {}
-                 ]}
-                 onPress={() => this.changeTab(true)}
-               >Fleet/PH Drivers</Text>
-             </View>
-             <Text style={styles.title}>Registration</Text>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Display Name</Text>
-               <TextInput
-                 style={styles.inputView} 
-                 placeholder='Display Name'
-                 maxLength={50}
-                 onChangeText={v => this.changeInfo('nickName', v)}
-               />
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Email Address</Text>
-               <TextInput
-                 style={styles.inputView}
-                 placeholder='Email Address'
-                 maxLength={50}
-                 onChangeText={v => this.changeInfo('email', v)}
-               />
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Phone Number</Text>
-               <View style={styles.mobileView}>
-                 <View style={styles.dropView}>
-                   <TextInput style={styles.dropInput} editable={false}/>
-                   <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
-                   <MaterialIcons name={'arrow-drop-down'} size={24} color={'#333'}/>
-                   <Dropdown
-                     style={styles.dropLayer}
-                     title='Country'
-                     prefixText="+"
-                     list={this.state.countryNums}
-                     value={this.state.countryNum}
-                     nameKey='countryNum'
-                     valueKey='countryNum'
-                     onChange={(value, index)=> {
-                       this.setState({
-                         countryNum: value
-                       })
-                     }}
-                     customerItemView={
-                       (item, index, onClick) => 
-                       <CountryDropNum
-                         key={index} 
-                         country={item}
-                         value={this.state.countryNum}
-                         onClick={onClick}/>
-                     }/>
-                 </View>
-                 <TextInput
-                   style={styles.contactView}
-                   placeholder='Mobile Number'
-                   keyboardType='phone-pad'
-                   maxLength={15}
-                   onChangeText={v => this.changeInfo('phone', v)}
-                 />
-               </View>
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Create Password</Text>
-               <TextInput
-                 secureTextEntry={this.state.wrongCount < 3}
-                 style={styles.inputView}
-                 placeholder='Password'
-                 maxLength={20}
-                 onChangeText={(value) => {
-                   this.applyStrength(value);
-                 }}
-               />
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}></Text>
-               <View style={styles.passwordView}>
-                 <View style={styles.strengthView}>
-                   <Text style={{fontSize:12, paddingRight: 4, color: '#333'}}>Password Strength</Text>
-                   <StrengthView {...this.state}/>
-                 </View>
-                 <View>
-                   <Text style={styles.passwordRole}>Your Password Must Have:</Text>
-                   <Text style={styles.passwordRole}>- 8 or more characters</Text>
-                   {/* <Text style={styles.passwordRole}>- upper and lower case letters</Text> */}
-                   <Text style={styles.passwordRole}>- at least one number</Text>
-                 </View>
-               </View>
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Confirm Password</Text>
-               <TextInput
-                 secureTextEntry={this.state.wrongCount < 3}
-                 style={styles.inputView}
-                 placeholder='Password'
-                 maxLength={20}
-                 onChangeText={v => this.changeInfo('password', v)}
-               />
-             </View>
-             { this.state.isFleetDriver &&
-               <>
-               <View style={styles.signInput}>
-                 <Text style={styles.inputLabel}>Your Company</Text>
-                 <Dropdown
-                   style={[styles.inputView, ui.flexc]}
-                   title='Company'
-                   list={this.state.companyList}
-                   value={this.state.fleetCompanyId}
-                   valueKey='fleetCompanyId'
-                   nameKey='fleetCompanyName'
-                   onChange={(value, index)=> {
-                     this.setState({
-                       fleetCompanyId: value
-                     })
-                   }}/>
-               </View>
-               <View style={styles.signInput}>
-                 <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
-                 <TextInput
-                   style={styles.inputView}
-                   placeholder='PH Driver Vocational Licence'
-                   maxLength={20}
-                   onChangeText={v => this.changeInfo('pdvLicence', v)}
-                 />
-               </View>
-               <View style={styles.signInput}>
-                 <Text style={styles.inputLabel}>{'  PDV Photos\n(Front & Back)'}</Text>
-                 <View style={styles.uploadGroup}>
-                   { this.state.pdvImages.map((item, index) => (
-                     <UploadView
-                       key={index}
-                       onPress={() => this.uploadImage(index)}
-                       url={item}/>
-                     )) 
-                   }
-                 </View>
-               </View>
-               </>
-             }
-             {/* <View style={styles.referView}>
-               <Text style={styles.referTitle}>Have a referral code?</Text>
-               <View style={styles.referText}>
-                 <Text>You'll get</Text>
-                 <Text style={styles.weight}>$5</Text>
-                 <Text>Credit as registration bonus!</Text>
-               </View>
-               <View style={styles.codeView}>
-                 <Text style={{ color: '#333', fontSize: 16 }}>Referral Code</Text>
-                 <TextInput
-                   style={styles.codeText}
-                   maxLength={6}
-                   placeholder='Referral Code'
-                   onChangeText={v => this.changeInfo('referralCode', v)}
-                 />
-               </View>
-             </View> */}
-             <View style={styles.agreeView}>
-               <CheckBox
-                 value={this.state.agree}
-                 onValueChange={v => this.changeAgree(v)}
-               />
-               <View style={styles.agreeTextRow}>
-                 <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
-                   {'I have read and I agree with the '}
-                 </Text>
-                 <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
-                 <Text style={styles.agreeText}>{' '}</Text>
-                 <Text style={styles.agreeText}>{'and '}</Text>
-                 <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
-                 <Text style={styles.agreeText}>.</Text>
-               </View>
-             </View>
-             <Button
-               style={styles.signButton}
-               elevation={1.5}
-               disabled={!this.state.agree}
-               text='SIGN UP'
-               fontSize={14}
-               onClick={() => {
-                 this.onRegister();
-               }}
-             />
-           </View>
-         </ScrollView>
-         <Modal
-           isVisible={this.state.visible}
-           onBackButtonPress={() => this.hideDialog()}
-           {...ModalProps}>
-           <RegisterDialog
-             address={this.state.email}
-             onClose={() => this.hideDialog()}
-           />
-         </Modal>
-       </View>
-     );
-   }
- }
- 
- const UploadView = ({url, onPress}) => (
-   <Pressable
-     style={styles.uploadView}
-     onPress={onPress}>
-     { url == ''
-     ? <Image
-         style={styles.uploadIcon}
-         source={require('../../images/icon/ic-add-photo.png')}/>
-     : <Image
-         style={styles.uploadIcon}
-         defaultSource={require('../../images/icon/icon-upload-default.png')}
-         source={{uri: host + url}}/>
-     }
-   </Pressable>
- )
- 
- const styles = StyleSheet.create({
-   header: {
-     paddingTop: 56,
-     backgroundColor: colorThemes
-   },
-   backView: {
-     top: 12,
-     zIndex: 5,
-     padding: 16,
-     position: 'absolute',
-     flexDirection: 'row'
-   },
-   backButton: {
-     borderRadius: 60,
-     backgroundColor: 'white'
-   },
-   backButtonView: {
-     height: 43,
-     paddingLeft: 16,
-     paddingRight: 16,
-     alignItems: 'center',
-     flexDirection: 'row'
-   },
-   scollView: {
-     flex: 1
-   },
-   logoView: {
-     paddingTop: 32,
-     paddingBottom: 56,
-     alignItems: 'center'
-   },
-   logoImg: {
-     width:165.09,
-     height: 51.94,
-   },
-   signView: {
-     flex: 1,
-     padding: 16,
-     marginTop: -30,
-     borderTopLeftRadius: 20,
-     borderTopRightRadius: 20,
-     backgroundColor: 'white'
-   },
-   tabView: {
-     marginBottom: 4,
-     borderWidth: 1,
-     borderRadius: 6,
-     overflow: 'hidden',
-     alignItems: 'center',
-     flexDirection: 'row',
-     borderColor: colorPrimary,
-   },
-   tabText: {
-     flex: 1,
-     color: '#333',
-     fontSize: 15,
-     textAlign: 'center',
-     ...$padding(10, 0),
-   },
-   tabActive: {
-     color: '#fff',
-     fontWeight: 'bold',
-     backgroundColor: colorPrimary
-   },
-   title: {
-     color: '#333',
-     fontSize: 18,
-     fontWeight: '700',
-     paddingTop: 4,
-     paddingBottom: 6,
-   },
-   signInput: {
-     marginTop: 16,
-     alignItems: 'center',
-     flexDirection: 'row'
-   },
-   inputLabel: {
-     flex: 1,
-     color: '#333',
-     fontSize: 14,
-     marginRight: 4
-   },
-   inputView: {
-     flex: 2,
-     color: '#333',
-     fontSize: 14,
-     borderRadius: 5,
-     minHeight: 40,
-     paddingTop: 6,
-     paddingLeft: 12,
-     paddingRight: 12,
-     paddingBottom: 6,
-     backgroundColor: '#F5F5F5'
-   },
-   mobileView: {
-     flex: 2,
-     marginLeft: -12,
-     alignItems: 'center',
-     flexDirection: 'row'
-   },
-   dropView: {
-     fontSize: 16,
-     borderRadius: 4,
-     paddingRight: 4,
-     flexDirection: 'row',
-     alignItems: 'center',
-     backgroundColor: '#F5F5F5'
-   },
-   dropInput: {
-     width: 12,
-     padding: 6,
-     color: '#333',
-     minHeight: 40,
-   },
-   countryText: {
-     color: '#333',
-     fontSize: 14,
-     paddingRight: 4
-   },
-   dropLayer: {
-     left: 0,
-     right: 0,
-     opacity: 0,
-     position: 'absolute'
-   },
-   contactView: {
-     flex: 1,
-     color: '#333',
-     fontSize: 15,
-     borderRadius: 4,
-     minHeight: 40,
-     paddingTop: 6,
-     paddingLeft: 12,
-     paddingRight: 12,
-     paddingBottom: 6,
-     marginLeft: 10,
-     backgroundColor: '#F5F5F5'
-   },
-   passwordView: {
-     flex: 2,
-     marginTop: -8,
-   },
-   strengthView: {
-     marginTop: -4,
-     alignItems: 'center',
-     paddingBottom: 4,
-     flexDirection: 'row'
-   },
-   passwordRole: {
-     color: '#999',
-     fontSize: 10,
-     lineHeight: 13
-   },
-   strengthItem: {
-     width: 14,
-     height: 2.5,
-     marginLeft: 8,
-     borderRadius: 3,
-     backgroundColor: '#CCC'
-   },
-   referView: {
-     padding: 16,
-     marginTop: 24,
-     borderRadius: 6,
-     alignItems: 'center',
-     backgroundColor: '#F5F5F5'
-   },
-   referTitle: {
-     color: '#333',
-     fontSize: 18,
-     fontWeight: 'bold'
-   },
-   referText: {
-     color: '#333',
-     paddingTop: 4,
-     alignItems: 'flex-end',
-     flexDirection: 'row'
-   },
-   weight: {
-     fontSize: 18,
-     paddingLeft: 4,
-     paddingRight: 4,
-     color: colorAccent,
-     fontWeight: 'bold'
-   },
-   codeView: {
-     paddingTop: 16,
-     alignItems: 'center',
-     flexDirection: 'row'
-   },
-   codeText: {
-     color: '#333',
-     fontSize: 16,
-     paddingTop: 6,
-     paddingLeft: 12,
-     paddingRight: 12,
-     paddingBottom: 6,
-     minHeight: 40,
-     marginLeft: 16,
-     borderRadius: 6,
-     textAlign: 'center',
-     backgroundColor: 'white'
-   },
-   signButton: {
-     marginTop: 24,
-     marginBottom: 8,
-   },
-   uploadGroup: {
-     flex: 2,
-     alignItems: 'center',
-     flexDirection: 'row',
-     justifyContent: 'center'
-   },
-   uploadView: {
-     flex: 1,
-     alignItems: 'center'
-   },
-   uploadIcon: {
-     width: $vw(28),
-     height: $vw(28),
-     borderRadius: 6
-   },
-   agreeView: {
-     marginTop: 24,
-     marginBottom: 16,
-     flexDirection: 'row',
-     alignItems: 'flex-start',
-   },
-   agreeTextRow: {
-     flex: 1,
-     paddingTop: 4,
-     paddingLeft: 8,
-     flexWrap: 'wrap',
-     flexDirection: 'row'
-   },
-   agreeText: {
-     color: '#333',
-     fontSize: 14,
-     paddingTop: 2,
-     paddingBottom: 2
-   },
-   agreeLink: {
-     ...ui.link,
-     fontSize: 14,
-     paddingTop: 2,
-     paddingBottom: 2,
-     textDecorationLine: 'underline'
-   }
- });
- 
+import StrengthView from './StrengthView';
+import { UploadThemes } from '../../components/ThemesConfig';
+
+const options = {
+  width: 300,
+  height: 200,
+  cropping: true,
+  multiple: false,
+  mediaType: 'photo',
+  writeTempFile: false,
+  compressImageQuality: 0.8,
+  compressImageMaxWidth: 720,
+  compressImageMaxHeight: 1280,
+  ...UploadThemes
+}
+
+export default class Register extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      agree: true,
+      strength: 0,
+      countryNum: '65',
+      countryShow: false,
+      userInfo: {},
+      countryNums: [],
+      wrongCount: 0,
+      params: {...this.props.route.params},
+      email: '',
+      password: '',
+      fleetCompanyId: '',
+      visible: false,
+      isFleetDriver: false,
+      pdvImages: ['', ''],
+      companyList: []
+    };
+  }
+
+  componentDidMount() {
+    //console.log(this.state.params);
+    this.getCountryList();
+    this.getCompanyList();
+  }
+
+  applyStrength(text) {
+    StrengthView.V4.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+    });
+  }
+
+  changeInfo(key, value) {
+    var info = this.state.userInfo;
+    info[key] = value;
+    this.setState({
+      'userInfo': info
+    });
+  }
+
+  changeTab(type) {
+    this.setState({
+      isFleetDriver: type
+    })
+  }
+
+  getCountryList() {
+    GetCountryList(list => {
+      this.setState({
+        countryNums: list
+      })
+    })
+  }
+
+  getCompanyList() {
+    apiUser.getConmpany().then(res => {
+      if (res.data) {
+        this.setState({
+          companyList: res.data
+        })
+      }
+    }).catch(err => [
+      toastShort(err)
+    ])
+  }
+
+  onRegister() {
+    //console.log('sign up', this.state);
+    var info = this.state.userInfo;
+    if (!info.nickName) {
+      toastShort('Please enter display name');
+      return;
+    }
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    if (!info.phone) {
+      toastShort('Please enter contact number');
+      return;
+    }
+    if (!/^\d{6,15}$/.test(info.phone)) {
+      toastShort('Phone Number is incorrect format');
+      return;
+    }
+    if (!this.state.password) {
+      toastShort('Please enter password');
+      return;
+    }
+    if (this.state.strength < StrengthView.V4.maxStrength) {
+      toastShort('Password is not strong');
+      return;
+    }
+    if (!info.password) {
+      toastShort('Please enter confirm password');
+      return;
+    }
+    if (info.password != this.state.password) {
+      toastShort('The twice passwords are inconsistent');
+      if (this.state.wrongCount < 3) {
+        this.setState({
+          wrongCount: this.state.wrongCount + 1
+        })
+      }
+      return;
+    }
+    if (this.state.isFleetDriver) {
+      if (!info.pdvLicence) {
+        toastShort('Please enter PDV Licence');
+        return;
+      }
+      if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
+        toastShort('Please upload PDV Licence Photos');
+        return;
+      }
+    }
+    let param = Object.assign({}, info);
+    //param.phone = this.state.countryNum + info.phone
+    param.callingCode = this.state.countryNum;
+    if (this.state.isFleetDriver) {
+      param.userType = 'Driver';
+      param.pdvLicencePictures = this.state.pdvImages;
+      param.fleetCompanyId = this.state.fleetCompanyId;
+    } else {
+      param.userType = 'Public';
+    }
+    console.log('params', param);
+    Dialog.showProgressDialog();
+    apiUser.register(param).then(res => {
+      Dialog.dismissLoading();
+      //toastShort('Sign up successfully!');
+      this.setState({
+        email: param.email,
+        visible: true
+      });
+      //this.backToLogin();
+    }).catch(err => {
+      toastShort(err);
+      Dialog.dismissLoading();
+    });
+  }
+  
+  getBackTopPosition() {
+    return isIOS ? statusHeight - 16 : 8;
+  }
+  
+  backToLogin() {
+    if (this.state.params.actionLogin) {
+      goBack()
+      startPage(PageList.login);
+    } else {
+      goBack();
+    }
+  }
+
+  uploadImage(index) {
+    ImagePicker.openPicker(options).then(image => {
+      if (image.path) {
+        apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
+          if (res.success && res.data.picturePath) {
+            let imageUrl = this.state.pdvImages;
+            imageUrl[index] = res.data.picturePath;
+            this.setState({
+              pdvImages: imageUrl
+            });
+          } else {
+            toastShort('Upload failed, please retry');
+          }
+        }).catch(err => {
+          toastShort(err);
+        });
+      }
+    }).catch(err => {
+      //console.log(err);
+    });
+  }
+
+  changeAgree(ag) {
+    this.setState({
+      agree: ag
+    })
+  }
+
+  hideDialog() {
+    this.setState({
+      visible: false
+    });
+    this.backToLogin();
+  }
+
+  render() {
+    return (
+      <View style={ui.container}>
+        <View style={Styles.toolbar}>
+          <BackButton/>
+          <View style={styles.logoView}>
+            <Image
+              source={require('../../images/app-logo.png')}
+              style={Styles.logo}
+              resizeMode='contain'
+            />
+          </View>
+        </View>
+        <ScrollView
+          style={styles.scollView}
+          keyboardShouldPersistTaps={'handled'}>
+          <View style={styles.signView}>
+            <View style={styles.tabView}>
+              <Text 
+                style={[
+                  styles.tabText, 
+                  this.state.isFleetDriver ? {} : styles.tabActive
+                ]}
+                onPress={() => this.changeTab(false)}
+              >Public Users</Text>
+              <Text
+                style={[
+                  styles.tabText,
+                  this.state.isFleetDriver ? styles.tabActive: {}
+                ]}
+                onPress={() => this.changeTab(true)}
+              >Fleet/PH Drivers</Text>
+            </View>
+            {/* <Text style={styles.title}>Registration</Text> */}
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Display Name</Text>
+              <TextInput
+                style={styles.inputView} 
+                placeholder='Display Name'
+                maxLength={50}
+                onChangeText={v => this.changeInfo('nickName', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Email Address</Text>
+              <TextInput
+                style={styles.inputView}
+                placeholder='Email Address'
+                maxLength={50}
+                onChangeText={v => this.changeInfo('email', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Phone Number</Text>
+              <View style={styles.mobileView}>
+                <View style={styles.dropView}>
+                  <TextInput style={styles.dropInput} editable={false}/>
+                  <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
+                  <MaterialIcons name={'arrow-drop-down'} size={24} color={colorDark}/>
+                  <Dropdown
+                    style={styles.dropLayer}
+                    title='Country'
+                    prefixText="+"
+                    list={this.state.countryNums}
+                    value={this.state.countryNum}
+                    nameKey='countryNum'
+                    valueKey='countryNum'
+                    onChange={(value, index)=> {
+                      this.setState({
+                        countryNum: value
+                      })
+                    }}
+                    customerItemView={
+                      (item, index, onClick) => 
+                      <CountryDropNum
+                        key={index} 
+                        country={item}
+                        value={this.state.countryNum}
+                        onClick={onClick}/>
+                    }/>
+                </View>
+                <TextInput
+                  style={styles.contactView}
+                  placeholder='Mobile Number'
+                  keyboardType='phone-pad'
+                  maxLength={15}
+                  onChangeText={v => this.changeInfo('phone', v)}
+                />
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Create Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={(value) => {
+                  this.applyStrength(value);
+                }}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}></Text>
+              <View style={styles.passwordView}>
+                <StrengthView.V4.VIEW {...this.state}/>
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Confirm Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={v => this.changeInfo('password', v)}
+              />
+            </View>
+            { this.state.isFleetDriver &&
+              <>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>Your Company</Text>
+                <Dropdown
+                  style={[styles.inputView, ui.flexc]}
+                  title='Company'
+                  list={this.state.companyList}
+                  value={this.state.fleetCompanyId}
+                  valueKey='fleetCompanyId'
+                  nameKey='fleetCompanyName'
+                  onChange={(value, index)=> {
+                    this.setState({
+                      fleetCompanyId: value
+                    })
+                  }}/>
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
+                <TextInput
+                  style={styles.inputView}
+                  placeholder='PH Driver Vocational Licence'
+                  maxLength={20}
+                  onChangeText={v => this.changeInfo('pdvLicence', v)}
+                />
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'  PDV Photos\n(Front & Back)'}</Text>
+                <View style={styles.uploadGroup}>
+                  { this.state.pdvImages.map((item, index) => (
+                    <UploadView
+                      key={index}
+                      onPress={() => this.uploadImage(index)}
+                      url={item}/>
+                    )) 
+                  }
+                </View>
+              </View>
+              </>
+            }
+            <View style={styles.referView}>
+              <Text style={styles.referTitle}>Have a referral code?</Text>
+              <View style={styles.referText}>
+                <Text>You'll get</Text>
+                <Text style={styles.weight}>$5</Text>
+                <Text>Credit as registration bonus!</Text>
+              </View>
+              <View style={styles.codeView}>
+                <Text style={{ color: textPrimary, fontSize: 16 }}>Referral Code</Text>
+                <TextInput
+                  style={styles.codeText}
+                  maxLength={6}
+                  placeholder='Referral Code'
+                  onChangeText={v => this.changeInfo('referralCode', v)}
+                />
+              </View>
+            </View>
+            <View style={styles.agreeView}>
+              <CheckBox
+                value={this.state.agree}
+                onValueChange={v => this.changeAgree(v)}
+              />
+              <View style={styles.agreeTextRow}>
+                <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
+                  {'I have read and I agree with the '}
+                </Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
+                <Text style={styles.agreeText}>{' '}</Text>
+                <Text style={styles.agreeText}>{'and '}</Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
+                <Text style={styles.agreeText}>.</Text>
+              </View>
+            </View>
+            <Button
+              style={styles.signButton}
+              elevation={1.5}
+              disabled={!this.state.agree}
+              text='SIGN UP'
+              fontSize={14}
+              onClick={() => {
+                this.onRegister();
+              }}
+            />
+          </View>
+        </ScrollView>
+        <Modal
+          isVisible={this.state.visible}
+          onBackButtonPress={() => this.hideDialog()}
+          {...ModalProps}>
+          <RegisterDialog
+            address={this.state.email}
+            onClose={() => this.hideDialog()}
+          />
+        </Modal>
+      </View>
+    );
+  }
+}
+
+const UploadView = ({url, onPress}) => (
+  <Pressable
+    style={styles.uploadView}
+    onPress={onPress}>
+    { url == ''
+    ? <Image
+        style={styles.uploadIcon}
+        source={require('../../images/icon/ic-add-photo.png')}/>
+    : <Image
+        style={styles.uploadIcon}
+        defaultSource={require('../../images/icon/icon-upload-default.png')}
+        source={{uri: host + url}}/>
+    }
+  </Pressable>
+)
+
+const styles = StyleSheet.create({
+  header: {
+    paddingTop: 56,
+    backgroundColor: colorThemes
+  },
+  backView: {
+    top: 12,
+    zIndex: 5,
+    padding: 16,
+    position: 'absolute',
+    flexDirection: 'row'
+  },
+  backButton: {
+    borderRadius: 60,
+    backgroundColor: colorLight
+  },
+  backButtonView: {
+    height: 43,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  scollView: {
+    flex: 1
+  },
+  logoView: {
+    flex: 1,
+    paddingRight: 48,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  logoImg: {
+    width:165.09,
+    height: 51.94,
+  },
+  signView: {
+    flex: 1,
+    padding: 16,
+    marginTop: 0,
+    borderTopLeftRadius: 20,
+    borderTopRightRadius: 20,
+    backgroundColor: colorLight
+  },
+  tabView: {
+    marginBottom: 4,
+    borderWidth: 1,
+    borderRadius: 60,
+    overflow: 'hidden',
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderColor: colorPrimary,
+  },
+  tabText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    textAlign: 'center',
+    ...$padding(10, 0),
+  },
+  tabActive: {
+    color: textLight,
+    fontWeight: 'bold',
+    backgroundColor: colorPrimary
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: '700',
+    paddingTop: 4,
+    paddingBottom: 6,
+  },
+  signInput: {
+    marginTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  inputLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    marginRight: 4
+  },
+  inputView: {
+    flex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    borderRadius: 5,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    backgroundColor: '#F5F5F5'
+  },
+  mobileView: {
+    flex: 2,
+    marginLeft: -12,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  dropView: {
+    fontSize: 16,
+    borderRadius: 4,
+    paddingRight: 4,
+    flexDirection: 'row',
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  dropInput: {
+    width: 12,
+    padding: 6,
+    color: textPrimary,
+    minHeight: 40,
+  },
+  countryText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingRight: 4
+  },
+  dropLayer: {
+    left: 0,
+    right: 0,
+    opacity: 0,
+    position: 'absolute'
+  },
+  contactView: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    borderRadius: 4,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    marginLeft: 10,
+    backgroundColor: '#F5F5F5'
+  },
+  passwordView: {
+    flex: 2,
+    marginTop: -8,
+  },
+  strengthView: {
+    marginTop: -4,
+    alignItems: 'center',
+    paddingBottom: 4,
+    flexDirection: 'row'
+  },
+  passwordRole: {
+    color: '#999',
+    fontSize: 10,
+    lineHeight: 13
+  },
+  strengthItem: {
+    width: 14,
+    height: 2.5,
+    marginLeft: 8,
+    borderRadius: 3,
+    backgroundColor: '#CCC'
+  },
+  referView: {
+    padding: 16,
+    marginTop: 24,
+    borderRadius: 6,
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  referTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  referText: {
+    color: textPrimary,
+    paddingTop: 4,
+    alignItems: 'flex-end',
+    flexDirection: 'row'
+  },
+  weight: {
+    fontSize: 18,
+    paddingLeft: 4,
+    paddingRight: 4,
+    color: colorAccent,
+    fontWeight: 'bold'
+  },
+  codeView: {
+    paddingTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  codeText: {
+    color: textPrimary,
+    fontSize: 16,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    minHeight: 40,
+    marginLeft: 16,
+    borderRadius: 6,
+    textAlign: 'center',
+    backgroundColor: colorLight
+  },
+  signButton: {
+    marginTop: 24,
+    marginBottom: 8,
+  },
+  uploadGroup: {
+    flex: 2,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center'
+  },
+  uploadView: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  uploadIcon: {
+    width: $vw(28),
+    height: $vw(28),
+    borderRadius: 6
+  },
+  agreeView: {
+    marginTop: 24,
+    marginBottom: 16,
+    flexDirection: 'row',
+    alignItems: 'flex-start',
+  },
+  agreeTextRow: {
+    flex: 1,
+    paddingTop: 4,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    flexDirection: 'row'
+  },
+  agreeText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2
+  },
+  agreeLink: {
+    ...ui.link,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2,
+    textDecorationLine: 'underline'
+  }
+});

+ 702 - 0
Strides-APP/app/pages/sign/RegisterV3.js

@@ -0,0 +1,702 @@
+/**
+ * 公共用户和司机用户注册页面
+ * @邠心vbe on 2023/02/01
+ */
+import React from 'react';
+import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import apiUser from '../../api/apiUser';
+import Button from '../../components/Button';
+import { PageList } from '../Router';
+import Dialog from '../../components/Dialog';
+import Modal from 'react-native-modal';
+import { RegisterDialog } from '../charge/InfoDialog';
+import Dropdown from '../../components/Dropdown';
+import ImagePicker from 'react-native-image-crop-picker';
+import apiUpload from '../../api/apiUpload';
+import { host } from '../../api/http';
+import { ModalProps } from '../../components/BottomModal';
+import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
+import StrengthView from './StrengthView';
+import CheckBox from '../../components/CheckBox';
+import { UploadThemes } from '../../components/ThemesConfig';
+
+const options = {
+  width: 300,
+  height: 200,
+  cropping: true,
+  multiple: false,
+  mediaType: 'photo',
+  writeTempFile: false,
+  compressImageQuality: 0.8,
+  compressImageMaxWidth: 720,
+  compressImageMaxHeight: 1280,
+  ...UploadThemes
+}
+
+export default class RegisterV3 extends React.Component {
+  constructor(props) {
+    super(props);
+    this.StrengthView = StrengthView.V1
+    this.state = {
+      agree: true,
+      strength: 0,
+      countryNum: '65',
+      countryShow: false,
+      userInfo: {},
+      countryNums: [],
+      wrongCount: 0,
+      params: {...this.props.route.params},
+      email: '',
+      password: '',
+      fleetCompanyId: '',
+      visible: false,
+      isFleetDriver: false,
+      pdvImages: ['', ''],
+      companyList: []
+    };
+  }
+
+  componentDidMount() {
+    //console.log(this.state.params);
+    if (this.state.params.isFleetUser) {
+      this.setState({
+        isFleetDriver: true
+      })
+      this.props.navigation.setOptions({
+        headerTitle: "Fleet / PHV Registration"
+      })
+    }
+    this.getCountryList();
+    this.getCompanyList();
+  }
+
+  applyStrength(text) {
+    this.StrengthView.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+    });
+  }
+
+  changeInfo(key, value) {
+    var info = this.state.userInfo;
+    info[key] = value;
+    this.setState({
+      'userInfo': info
+    });
+  }
+
+  getCountryList() {
+    GetCountryList(list => {
+      this.setState({
+        countryNums: list
+      })
+    })
+  }
+
+  getCompanyList() {
+    apiUser.getConmpany().then(res => {
+      if (res.data) {
+        this.setState({
+          companyList: res.data
+        })
+      }
+    }).catch(err => [
+      toastShort(err)
+    ])
+  }
+
+  onRegister() {
+    //console.log('sign up', this.state);
+    var info = this.state.userInfo;
+    if (!info.nickName) {
+      toastShort('Please enter display name');
+      return;
+    }
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    if (!info.phone) {
+      toastShort('Please enter contact number');
+      return;
+    }
+    if (!/^\d{6,15}$/.test(info.phone)) {
+      toastShort('Phone Number is incorrect format');
+      return;
+    }
+    if (!this.state.password) {
+      toastShort('Please enter password');
+      return;
+    }
+    if (this.state.strength < this.StrengthView.maxStrength) {
+      toastShort('Password is not strong');
+      return;
+    }
+    if (!info.password) {
+      toastShort('Please enter confirm password');
+      return;
+    }
+    if (info.password != this.state.password) {
+      toastShort('The twice passwords are inconsistent');
+      if (this.state.wrongCount < 3) {
+        this.setState({
+          wrongCount: this.state.wrongCount + 1
+        })
+      }
+      return;
+    }
+    if (this.state.isFleetDriver) {
+      if (!info.pdvLicence) {
+        toastShort('Please enter PDV Licence');
+        return;
+      }
+      if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
+        toastShort('Please upload PDV Licence Photos');
+        return;
+      }
+    }
+    let param = Object.assign({}, info);
+    //param.phone = this.state.countryNum + info.phone
+    param.callingCode = this.state.countryNum;
+    if (this.state.isFleetDriver) {
+      param.userType = 'Driver';
+      param.pdvLicencePictures = this.state.pdvImages;
+      param.fleetCompanyId = this.state.fleetCompanyId;
+    } else {
+      param.userType = 'Public';
+    }
+    console.log('params', param);
+    Dialog.showProgressDialog();
+    apiUser.register(param).then(res => {
+      Dialog.dismissLoading();
+      //toastShort('Sign up successfully!');
+      this.setState({
+        email: param.email,
+        visible: true
+      });
+      //this.backToLogin();
+    }).catch(err => {
+      toastShort(err);
+      Dialog.dismissLoading();
+    });
+  }
+  
+  getBackTopPosition() {
+    return isIOS ? statusHeight - 16 : 8;
+  }
+  
+  backToLogin() {
+    if (this.state.params.actionLogin) {
+      goBack()
+      startPage(PageList.login);
+    } else {
+      goBack();
+    }
+  }
+
+  uploadImage(index) {
+    ImagePicker.openPicker(options).then(image => {
+      if (image.path) {
+        apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
+          if (res.success && res.data.picturePath) {
+            let imageUrl = this.state.pdvImages;
+            imageUrl[index] = res.data.picturePath;
+            this.setState({
+              pdvImages: imageUrl
+            });
+          } else {
+            toastShort('Upload failed, please retry');
+          }
+        }).catch(err => {
+          toastShort(err);
+        });
+      }
+    }).catch(err => {
+      //console.log(err);
+    });
+  }
+
+  changeAgree(ag) {
+    this.setState({
+      agree: ag
+    })
+  }
+
+  hideDialog() {
+    this.setState({
+      visible: false
+    });
+    this.backToLogin();
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <ScrollView
+          style={styles.scollView}
+          keyboardShouldPersistTaps={'handled'}>
+          <View style={styles.signView}>
+            {/* <View style={styles.tabView}>
+              <Text 
+                style={[
+                  styles.tabText, 
+                  this.state.isFleetDriver ? {} : styles.tabActive
+                ]}
+                onPress={() => this.changeTab(false)}
+              >Public Users</Text>
+              <Text
+                style={[
+                  styles.tabText,
+                  this.state.isFleetDriver ? styles.tabActive: {}
+                ]}
+                onPress={() => this.changeTab(true)}
+              >Fleet/PH Drivers</Text>
+            </View> */}
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Display Name</Text>
+              <TextInput
+                style={styles.inputView} 
+                placeholder='Display Name'
+                maxLength={50}
+                onChangeText={v => this.changeInfo('nickName', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Email Address</Text>
+              <TextInput
+                style={styles.inputView}
+                placeholder='Email Address'
+                maxLength={50}
+                keyboardType="email-address"
+                textContentType='emailAddress'
+                onChangeText={v => this.changeInfo('email', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Phone Number</Text>
+              <View style={styles.mobileView}>
+                <View style={styles.dropView}>
+                  <TextInput style={styles.dropInput} editable={false}/>
+                  <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
+                  <MaterialIcons name={'arrow-drop-down'} size={24} color={colorDark}/>
+                  <Dropdown
+                    style={styles.dropLayer}
+                    title='Country'
+                    prefixText="+"
+                    list={this.state.countryNums}
+                    value={this.state.countryNum}
+                    nameKey='countryNum'
+                    valueKey='countryNum'
+                    onChange={(value, index)=> {
+                      this.setState({
+                        countryNum: value
+                      })
+                    }}
+                    customerItemView={
+                      (item, index, onClick) => 
+                      <CountryDropNum
+                        key={index} 
+                        country={item}
+                        value={this.state.countryNum}
+                        onClick={onClick}/>
+                    }/>
+                </View>
+                <TextInput
+                  style={styles.contactView}
+                  placeholder='Mobile Number'
+                  keyboardType='phone-pad'
+                  maxLength={15}
+                  onChangeText={v => this.changeInfo('phone', v)}
+                />
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Create Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={(value) => {
+                  this.applyStrength(value);
+                }}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}></Text>
+              <View style={styles.passwordView}>
+                <this.StrengthView.VIEW strength={this.state.strength}/>
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Confirm Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={v => this.changeInfo('password', v)}
+              />
+            </View>
+            { this.state.isFleetDriver &&
+              <>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>Your Company</Text>
+                <Dropdown
+                  style={[styles.inputView, ui.flexc]}
+                  title='Company'
+                  list={this.state.companyList}
+                  value={this.state.fleetCompanyId}
+                  valueKey='fleetCompanyId'
+                  nameKey='fleetCompanyName'
+                  onChange={(value, index)=> {
+                    this.setState({
+                      fleetCompanyId: value
+                    })
+                  }}/>
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
+                <TextInput
+                  style={styles.inputView}
+                  placeholder='PH Driver Vocational Licence'
+                  maxLength={20}
+                  onChangeText={v => this.changeInfo('pdvLicence', v)}
+                />
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'  PDV Photos\n(Front & Back)'}</Text>
+                <View style={styles.uploadGroup}>
+                  { this.state.pdvImages.map((item, index) => (
+                    <UploadView
+                      key={index}
+                      onPress={() => this.uploadImage(index)}
+                      url={item}/>
+                    )) 
+                  }
+                </View>
+              </View>
+              </>
+            }
+            {/* <View style={styles.referView}>
+              <Text style={styles.referTitle}>Have a referral code?</Text>
+              <View style={styles.referText}>
+                <Text>You'll get</Text>
+                <Text style={styles.weight}>$5</Text>
+                <Text>Credit as registration bonus!</Text>
+              </View>
+              <View style={styles.codeView}>
+                <Text style={{ color: textPrimary, fontSize: 16 }}>Referral Code</Text>
+                <TextInput
+                  style={styles.codeText}
+                  maxLength={6}
+                  placeholder='Referral Code'
+                  onChangeText={v => this.changeInfo('referralCode', v)}
+                />
+              </View>
+            </View> */}
+            <View style={styles.agreeView}>
+              <CheckBox
+                value={this.state.agree}
+                onValueChange={v => this.changeAgree(v)}
+              />
+              <View style={styles.agreeTextRow}>
+                <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
+                  {'I have read and I agree with the '}
+                </Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
+                <Text style={styles.agreeText}>{' '}</Text>
+                <Text style={styles.agreeText}>{'and '}</Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
+                <Text style={styles.agreeText}>.</Text>
+              </View>
+            </View>
+            <Button
+              style={styles.signButton}
+              elevation={1.5}
+              disabled={!this.state.agree}
+              text='SIGN UP'
+              fontSize={14}
+              onClick={() => {
+                this.onRegister();
+              }}
+            />
+          </View>
+        </ScrollView>
+        <Modal
+          isVisible={this.state.visible}
+          onBackButtonPress={() => this.hideDialog()}
+          {...ModalProps}>
+          <RegisterDialog
+            address={this.state.email}
+            onClose={() => this.hideDialog()}
+          />
+        </Modal>
+      </View>
+    );
+  }
+}
+
+const UploadView = ({url, onPress}) => (
+  <Pressable
+    style={styles.uploadView}
+    onPress={onPress}>
+    { url == ''
+    ? <Image
+        style={styles.uploadIcon}
+        source={require('../../images/icon/ic-add-photo.png')}/>
+    : <Image
+        style={styles.uploadIcon}
+        defaultSource={require('../../images/icon/icon-upload-default.png')}
+        source={{uri: host + url}}/>
+    }
+  </Pressable>
+)
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  header: {
+    paddingTop: 56,
+    backgroundColor: colorAccent
+  },
+  backView: {
+    top: 12,
+    zIndex: 5,
+    padding: 16,
+    position: 'absolute',
+    flexDirection: 'row'
+  },
+  backButton: {
+    borderRadius: 60,
+    backgroundColor: colorLight
+  },
+  backButtonView: {
+    height: 43,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  scollView: {
+    flex: 1
+  },
+  logoView: {
+    paddingTop: 32,
+    paddingBottom: 56,
+    alignItems: 'center'
+  },
+  logoImg: {
+    width:165.09,
+    height: 51.94,
+  },
+  signView: {
+    flex: 1,
+    padding: 16,
+    //marginTop: -30,
+    borderTopLeftRadius: 20,
+    borderTopRightRadius: 20,
+    backgroundColor: colorLight
+  },
+  tabView: {
+    marginBottom: 4,
+    borderWidth: 1,
+    borderRadius: 6,
+    overflow: 'hidden',
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderColor: colorAccent,
+  },
+  tabText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    textAlign: 'center',
+    ...$padding(10, 0),
+  },
+  tabActive: {
+    fontWeight: 'bold',
+    backgroundColor: colorAccent
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: '700',
+    paddingTop: 4,
+    paddingBottom: 6,
+  },
+  signInput: {
+    marginTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  inputLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    marginRight: 4
+  },
+  inputView: {
+    flex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    borderRadius: 5,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    backgroundColor: '#F5F5F5'
+  },
+  mobileView: {
+    flex: 2,
+    marginLeft: -12,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  dropView: {
+    fontSize: 16,
+    borderRadius: 4,
+    paddingRight: 4,
+    flexDirection: 'row',
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  dropInput: {
+    width: 12,
+    padding: 6,
+    color: textPrimary,
+    minHeight: 40,
+  },
+  countryText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingRight: 4
+  },
+  dropLayer: {
+    left: 0,
+    right: 0,
+    opacity: 0,
+    position: 'absolute'
+  },
+  contactView: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    borderRadius: 4,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    marginLeft: 10,
+    backgroundColor: '#F5F5F5'
+  },
+  passwordView: {
+    flex: 2,
+    marginTop: -8,
+  },
+  referView: {
+    padding: 16,
+    marginTop: 24,
+    borderRadius: 6,
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  referTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  referText: {
+    color: textPrimary,
+    paddingTop: 4,
+    alignItems: 'flex-end',
+    flexDirection: 'row'
+  },
+  weight: {
+    fontSize: 18,
+    paddingLeft: 4,
+    paddingRight: 4,
+    color: colorAccent,
+    fontWeight: 'bold'
+  },
+  codeView: {
+    paddingTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  codeText: {
+    color: textPrimary,
+    fontSize: 16,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    minHeight: 40,
+    marginLeft: 16,
+    borderRadius: 6,
+    textAlign: 'center',
+    backgroundColor: colorLight
+  },
+  signButton: {
+    marginTop: 24,
+    marginBottom: 8,
+  },
+  uploadGroup: {
+    flex: 2,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center'
+  },
+  uploadView: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  uploadIcon: {
+    width: $vw(28),
+    height: $vw(28),
+    borderRadius: 6
+  },
+  agreeView: {
+    marginTop: 24,
+    marginBottom: 16,
+    flexDirection: 'row',
+    alignItems: 'flex-start',
+  },
+  agreeTextRow: {
+    flex: 1,
+    paddingTop: 4,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    flexDirection: 'row'
+  },
+  agreeText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2
+  },
+  agreeLink: {
+    ...ui.link,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2,
+    textDecorationLine: 'underline'
+  }
+});
+ 

+ 779 - 0
Strides-APP/app/pages/sign/RegisterV4.js

@@ -0,0 +1,779 @@
+/**
+ * 带OTP的注册页面
+ * @邠心vbe on 2023/02/06
+ */
+import React from 'react';
+import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import apiUser from '../../api/apiUser';
+import Button from '../../components/Button';
+import { PageList } from '../Router';
+import Dialog from '../../components/Dialog';
+import Modal from 'react-native-modal';
+import { RegisterDialog } from '../charge/InfoDialog';
+import Dropdown from '../../components/Dropdown';
+import ImagePicker from 'react-native-image-crop-picker';
+import apiUpload from '../../api/apiUpload';
+import { host } from '../../api/http';
+import { ModalProps } from '../../components/BottomModal';
+import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
+import StrengthView from './StrengthView';
+import CheckBox from '../../components/CheckBox';
+import { UploadThemes } from '../../components/ThemesConfig';
+
+const options = {
+  width: 300,
+  height: 200,
+  cropping: true,
+  multiple: false,
+  mediaType: 'photo',
+  writeTempFile: false,
+  compressImageQuality: 0.8,
+  compressImageMaxWidth: 720,
+  compressImageMaxHeight: 1280,
+  ...UploadThemes
+}
+
+export default class RegisterV4 extends React.Component {
+  constructor(props) {
+    super(props);
+    this.StrengthView = StrengthView.V2
+    this.state = {
+      agree: true,
+      strength: 0,
+      countryNum: '65',
+      countryShow: false,
+      userInfo: {
+        email: "",
+        verificationCode: ""
+      },
+      countryNums: [],
+      wrongCount: 0,
+      params: {...this.props.route.params},
+      email: '',
+      password: '',
+      fleetCompanyId: '',
+      sendMinutes: 0,
+      visible: false,
+      isFleetDriver: false,
+      pdvImages: ['', ''],
+      companyList: []
+    };
+  }
+
+  componentDidMount() {
+    //console.log(this.state.params);
+    if (this.state.params.isFleetUser) {
+      this.setState({
+        isFleetDriver: true
+      })
+      this.props.navigation.setOptions({
+        headerTitle: "Fleet / PHV Registration"
+      })
+    }
+    this.getCountryList();
+    this.getCompanyList();
+  }
+
+  applyStrength(text) {
+    this.StrengthView.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+    });
+  }
+
+  changeInfo(key, value) {
+    var info = this.state.userInfo;
+    info[key] = value;
+    this.setState({
+      'userInfo': info
+    });
+  }
+
+  getCountryList() {
+    GetCountryList(list => {
+      this.setState({
+        countryNums: list
+      })
+    })
+  }
+
+  getCompanyList() {
+    apiUser.getConmpany().then(res => {
+      if (res.data) {
+        this.setState({
+          companyList: res.data
+        })
+      }
+    }).catch(err => [
+      toastShort(err)
+    ])
+  }
+
+  sendVerification() {
+    var info = this.state.userInfo;
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    Dialog.showProgressDialog()
+    apiUser.sendVerificationCodeV2({email: info.email, type: "register"}).then(res => {
+      Dialog.dismissLoading()
+      //this.state.sendMinutes = 60;
+      this.state.sendMinutes = res.data?.resendTime ?? 60;
+      toastShort('Send verification code successfully');
+      this.contdownTime();
+    }).catch(err => {
+      Dialog.dismissLoading()
+      toastShort(err);
+    });
+  }
+
+  contdownTime() {
+    if (this.state.sendMinutes > 0) {
+      this.setState({
+        sendMinutes: this.state.sendMinutes - 1
+      })
+      setTimeout(() => {
+        this.contdownTime();
+      }, 1000);
+    }
+  }
+
+  onRegister() {
+    //console.log('sign up', this.state);
+    var info = this.state.userInfo;
+    if (!info.nickName) {
+      toastShort('Please enter display name');
+      return;
+    }
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    if (!info.phone) {
+      toastShort('Please enter contact number');
+      return;
+    }
+    if (!/^\d{6,15}$/.test(info.phone)) {
+      toastShort('Phone Number is incorrect format');
+      return;
+    }
+    if (!this.state.password) {
+      toastShort('Please enter password');
+      return;
+    }
+    if (this.state.strength < this.StrengthView.maxStrength) {
+      toastShort('Password is not strong');
+      return;
+    }
+    if (!info.password) {
+      toastShort('Please enter confirm password');
+      return;
+    }
+    if (info.password != this.state.password) {
+      toastShort('The twice passwords are inconsistent');
+      if (this.state.wrongCount < 3) {
+        this.setState({
+          wrongCount: this.state.wrongCount + 1
+        })
+      }
+      return;
+    }
+    if (this.state.isFleetDriver) {
+      if (!info.pdvLicence) {
+        toastShort('Please enter PDV Licence');
+        return;
+      }
+      if (this.state.pdvImages[0] == '' || this.state.pdvImages[1] == '') {
+        toastShort('Please upload PDV Licence Photos');
+        return;
+      }
+    }
+    let param = Object.assign({}, info);
+    //param.phone = this.state.countryNum + info.phone
+    param.callingCode = this.state.countryNum;
+    if (this.state.isFleetDriver) {
+      param.userType = 'Driver';
+      param.pdvLicencePictures = this.state.pdvImages;
+      param.fleetCompanyId = this.state.fleetCompanyId;
+    } else {
+      param.userType = 'Public';
+    }
+    param.otp = param.verificationCode;
+    console.log('params', param);
+    Dialog.showProgressDialog();
+    apiUser.register(param).then(res => {
+      Dialog.dismissLoading();
+      //toastShort('Sign up successfully!');
+      this.setState({
+        email: param.email,
+        visible: true
+      });
+      //this.backToLogin();
+    }).catch(err => {
+      toastShort(err);
+      Dialog.dismissLoading();
+    });
+  }
+  
+  getBackTopPosition() {
+    return isIOS ? statusHeight - 16 : 8;
+  }
+  
+  backToLogin() {
+    if (this.state.params.actionLogin) {
+      goBack()
+      startPage(PageList.login);
+    } else {
+      goBack();
+    }
+  }
+
+  uploadImage(index) {
+    ImagePicker.openPicker(options).then(image => {
+      if (image.path) {
+        apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
+          if (res.success && res.data.picturePath) {
+            let imageUrl = this.state.pdvImages;
+            imageUrl[index] = res.data.picturePath;
+            this.setState({
+              pdvImages: imageUrl
+            });
+          } else {
+            toastShort('Upload failed, please retry');
+          }
+        }).catch(err => {
+          toastShort(err);
+        });
+      }
+    }).catch(err => {
+      //console.log(err);
+    });
+  }
+
+  changeAgree(ag) {
+    this.setState({
+      agree: ag
+    })
+  }
+
+  hideDialog() {
+    this.setState({
+      visible: false
+    });
+    this.backToLogin();
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <ScrollView
+          style={styles.scollView}
+          keyboardShouldPersistTaps={'handled'}>
+          <View style={styles.signView}>
+            {/* <View style={styles.tabView}>
+              <Text 
+                style={[
+                  styles.tabText, 
+                  this.state.isFleetDriver ? {} : styles.tabActive
+                ]}
+                onPress={() => this.changeTab(false)}
+              >Public Users</Text>
+              <Text
+                style={[
+                  styles.tabText,
+                  this.state.isFleetDriver ? styles.tabActive: {}
+                ]}
+                onPress={() => this.changeTab(true)}
+              >Fleet/PH Drivers</Text>
+            </View> */}
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Display Name</Text>
+              <TextInput
+                style={styles.inputView} 
+                placeholder='Display Name'
+                maxLength={50}
+                onChangeText={v => this.changeInfo('nickName', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Email Address</Text>
+              <TextInput
+                style={styles.inputView}
+                placeholder='Email Address'
+                maxLength={50}
+                keyboardType="email-address"
+                textContentType='emailAddress'
+                onChangeText={v => this.changeInfo('email', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={[styles.inputLabel, {flex: 2}]}>Validate Email</Text>
+              <TextInput
+                style={[styles.inputView, {flex: 2.2, marginLeft: 2}]}
+                placeholder='OTP'
+                maxLength={6}
+                keyboardType="number-pad"
+                textContentType="telephoneNumber"
+                onChangeText={v => this.changeInfo('verificationCode', v)}
+              />
+              <Button
+                text={this.state.sendMinutes > 0 ? (this.state.sendMinutes + " s") : 'EMAIL OTP'}
+                style={styles.sendBtn}
+                disabled={this.state.sendMinutes > 0}
+                viewStyle={styles.sendBtnView}
+                textStyle={styles.sendBtnText}
+                onClick={() => this.sendVerification()}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Phone Number</Text>
+              <View style={styles.mobileView}>
+                <View style={styles.dropView}>
+                  <TextInput style={styles.dropInput} editable={false}/>
+                  <Text style={styles.countryText}>{"+" + this.state.countryNum}</Text>
+                  <MaterialIcons name={'arrow-drop-down'} size={24} color={colorDark}/>
+                  <Dropdown
+                    style={styles.dropLayer}
+                    title='Country'
+                    prefixText="+"
+                    list={this.state.countryNums}
+                    value={this.state.countryNum}
+                    nameKey='countryNum'
+                    valueKey='countryNum'
+                    onChange={(value, index)=> {
+                      this.setState({
+                        countryNum: value
+                      })
+                    }}
+                    customerItemView={
+                      (item, index, onClick) => 
+                      <CountryDropNum
+                        key={index} 
+                        country={item}
+                        value={this.state.countryNum}
+                        onClick={onClick}/>
+                    }/>
+                </View>
+                <TextInput
+                  style={styles.contactView}
+                  placeholder='Mobile Number'
+                  keyboardType='phone-pad'
+                  maxLength={15}
+                  onChangeText={v => this.changeInfo('phone', v)}
+                />
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Create Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={(value) => {
+                  this.applyStrength(value);
+                }}
+              />
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}></Text>
+              <View style={styles.passwordView}>
+                <this.StrengthView.VIEW strength={this.state.strength}/>
+              </View>
+            </View>
+            <View style={styles.signInput}>
+              <Text style={styles.inputLabel}>Confirm Password</Text>
+              <TextInput
+                secureTextEntry={this.state.wrongCount < 3}
+                style={styles.inputView}
+                placeholder='Password'
+                maxLength={20}
+                onChangeText={v => this.changeInfo('password', v)}
+              />
+            </View>
+            { this.state.isFleetDriver &&
+              <>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>Your Company</Text>
+                <Dropdown
+                  style={[styles.inputView, ui.flexc]}
+                  title='Company'
+                  list={this.state.companyList}
+                  value={this.state.fleetCompanyId}
+                  valueKey='fleetCompanyId'
+                  nameKey='fleetCompanyName'
+                  onChange={(value, index)=> {
+                    this.setState({
+                      fleetCompanyId: value
+                    })
+                  }}/>
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'PDV Licence'}</Text>
+                <TextInput
+                  style={styles.inputView}
+                  placeholder='PH Driver Vocational Licence'
+                  maxLength={20}
+                  onChangeText={v => this.changeInfo('pdvLicence', v)}
+                />
+              </View>
+              <View style={styles.signInput}>
+                <Text style={styles.inputLabel}>{'  PDV Photos\n(Front & Back)'}</Text>
+                <View style={styles.uploadGroup}>
+                  { this.state.pdvImages.map((item, index) => (
+                    <UploadView
+                      key={index}
+                      onPress={() => this.uploadImage(index)}
+                      url={item}/>
+                    )) 
+                  }
+                </View>
+              </View>
+              </>
+            }
+            {/* <View style={styles.referView}>
+              <Text style={styles.referTitle}>Have a referral code?</Text>
+              <View style={styles.referText}>
+                <Text>You'll get</Text>
+                <Text style={styles.weight}>$5</Text>
+                <Text>Credit as registration bonus!</Text>
+              </View>
+              <View style={styles.codeView}>
+                <Text style={{ color: textPrimary, fontSize: 16 }}>Referral Code</Text>
+                <TextInput
+                  style={styles.codeText}
+                  maxLength={6}
+                  placeholder='Referral Code'
+                  onChangeText={v => this.changeInfo('referralCode', v)}
+                />
+              </View>
+            </View> */}
+            <View style={styles.agreeView}>
+              <CheckBox
+                value={this.state.agree}
+                onValueChange={v => this.changeAgree(v)}
+              />
+              <View style={styles.agreeTextRow}>
+                <Text style={styles.agreeText} onPress={() => this.changeAgree(!this.state.agree)}>
+                  {'I have read and I agree with the '}
+                </Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.condition)}>Terms of Use</Text>
+                <Text style={styles.agreeText}>{' '}</Text>
+                <Text style={styles.agreeText}>{'and '}</Text>
+                <Text style={styles.agreeLink} onPress={() => startPage(PageList.privacy)}>Privacy Policy</Text>
+                <Text style={styles.agreeText}>.</Text>
+              </View>
+            </View>
+            <Button
+              style={styles.signButton}
+              elevation={1.5}
+              disabled={!this.state.agree}
+              text='SIGN UP'
+              fontSize={14}
+              onClick={() => {
+                this.onRegister();
+              }}
+            />
+          </View>
+        </ScrollView>
+        <Modal
+          isVisible={this.state.visible}
+          onBackButtonPress={() => this.hideDialog()}
+          {...ModalProps}>
+          <RegisterDialog
+            address={this.state.email}
+            onClose={() => this.hideDialog()}
+          />
+        </Modal>
+      </View>
+    );
+  }
+}
+
+const UploadView = ({url, onPress}) => (
+  <Pressable
+    style={styles.uploadView}
+    onPress={onPress}>
+    { url == ''
+    ? <Image
+        style={styles.uploadIcon}
+        source={require('../../images/icon/ic-add-photo.png')}/>
+    : <Image
+        style={styles.uploadIcon}
+        defaultSource={require('../../images/icon/icon-upload-default.png')}
+        source={{uri: host + url}}/>
+    }
+  </Pressable>
+)
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  header: {
+    paddingTop: 56,
+    backgroundColor: colorAccent
+  },
+  backView: {
+    top: 12,
+    zIndex: 5,
+    padding: 16,
+    position: 'absolute',
+    flexDirection: 'row'
+  },
+  backButton: {
+    borderRadius: 60,
+    backgroundColor: colorLight
+  },
+  backButtonView: {
+    height: 43,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  scollView: {
+    flex: 1
+  },
+  logoView: {
+    paddingTop: 32,
+    paddingBottom: 56,
+    alignItems: 'center'
+  },
+  logoImg: {
+    width:165.09,
+    height: 51.94,
+  },
+  signView: {
+    flex: 1,
+    padding: 16,
+    //marginTop: -30,
+    borderTopLeftRadius: 20,
+    borderTopRightRadius: 20,
+    backgroundColor: colorLight
+  },
+  tabView: {
+    marginBottom: 4,
+    borderWidth: 1,
+    borderRadius: 6,
+    overflow: 'hidden',
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderColor: colorAccent,
+  },
+  tabText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    textAlign: 'center',
+    ...$padding(10, 0),
+  },
+  tabActive: {
+    fontWeight: 'bold',
+    backgroundColor: colorAccent
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: '700',
+    paddingTop: 4,
+    paddingBottom: 6,
+  },
+  signInput: {
+    marginTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  inputLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    marginRight: 4
+  },
+  inputView: {
+    flex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    borderRadius: 5,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    backgroundColor: '#F5F5F5'
+  },
+  mobileView: {
+    flex: 2,
+    marginLeft: -12,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  dropView: {
+    fontSize: 16,
+    borderRadius: 4,
+    paddingRight: 4,
+    flexDirection: 'row',
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  dropInput: {
+    width: 12,
+    padding: 6,
+    color: textPrimary,
+    minHeight: 40,
+  },
+  countryText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingRight: 4
+  },
+  dropLayer: {
+    left: 0,
+    right: 0,
+    opacity: 0,
+    position: 'absolute'
+  },
+  contactView: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    borderRadius: 4,
+    minHeight: 40,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    marginLeft: 10,
+    backgroundColor: '#F5F5F5'
+  },
+  passwordView: {
+    flex: 2,
+    marginTop: -8,
+  },
+  referView: {
+    padding: 16,
+    marginTop: 24,
+    borderRadius: 6,
+    alignItems: 'center',
+    backgroundColor: '#F5F5F5'
+  },
+  referTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold'
+  },
+  referText: {
+    color: textPrimary,
+    paddingTop: 4,
+    alignItems: 'flex-end',
+    flexDirection: 'row'
+  },
+  weight: {
+    fontSize: 18,
+    paddingLeft: 4,
+    paddingRight: 4,
+    color: colorAccent,
+    fontWeight: 'bold'
+  },
+  codeView: {
+    paddingTop: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  codeText: {
+    color: textPrimary,
+    fontSize: 16,
+    paddingTop: 6,
+    paddingLeft: 12,
+    paddingRight: 12,
+    paddingBottom: 6,
+    minHeight: 40,
+    marginLeft: 16,
+    borderRadius: 6,
+    textAlign: 'center',
+    backgroundColor: colorLight
+  },
+  signButton: {
+    marginTop: 24,
+    marginBottom: 8,
+  },
+  uploadGroup: {
+    flex: 2,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center'
+  },
+  uploadView: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  uploadIcon: {
+    width: $vw(28),
+    height: $vw(28),
+    borderRadius: 6
+  },
+  agreeView: {
+    marginTop: 24,
+    marginBottom: 16,
+    flexDirection: 'row',
+    alignItems: 'flex-start',
+  },
+  agreeTextRow: {
+    flex: 1,
+    paddingTop: 4,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    flexDirection: 'row'
+  },
+  agreeText: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2
+  },
+  agreeLink: {
+    ...ui.link,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 2,
+    textDecorationLine: 'underline'
+  },
+  sendBtn: {
+    flex: 1.8,
+    marginLeft: 6,
+    marginRight: 0,
+    borderRadius: 6
+  },
+  sendBtnView: {
+    flex: 1,
+    height: 40,
+    paddingLeft: 4,
+    paddingRight: 4,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  sendBtnText: {
+    color: colorLight,
+    fontSize: 13,
+    fontWeight: 'bold'
+  },
+});
+ 

+ 12 - 11
Strides-APP/app/pages/sign/ResetPassword.js

@@ -154,7 +154,8 @@ export default class ResetPassword extends Component {
             <View style={styles.logoView}>
               <Image
                 style={styles.logoImg}
-                source={require('../../images/tool-logo.png')}/>
+                resizeMode="contain"
+                source={require('../../images/app-logo.png')}/>
             </View>
           </View>
           <View style={{...styles.backView, top: this.getBackTopPosition()}}>
@@ -163,7 +164,7 @@ export default class ResetPassword extends Component {
               viewStyle={styles.backButtonView}
               onClick={() => goBack()}>
               <BackIcon/>
-              <Text style={{color: '#333',fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
+              <Text style={{color: textPrimary,fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
             </Button>
           </View>
           <View style={styles.resetView}>
@@ -193,7 +194,7 @@ export default class ResetPassword extends Component {
               <Text style={styles.inputLabel}></Text>
               <View style={styles.passwordView}>
                 <View style={styles.strengthView}>
-                  <Text style={{fontSize:12, paddingRight: 4, color: '#333'}}>Password Strength</Text>
+                  <Text style={{fontSize:12, paddingRight: 4, color: textPrimary}}>Password Strength</Text>
                   <StrengthView {...this.state}/>
                 </View>
                 <View>
@@ -258,7 +259,7 @@ const styles = StyleSheet.create({
   },
   backButton: {
     borderRadius: 60,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   backButtonView: {
     height: 43,
@@ -269,7 +270,7 @@ const styles = StyleSheet.create({
   },
   scollView: {
     flex: 1,
-    backgroundColor: 'white'
+    backgroundColor: pageBackground
   },
   logoView: {
     paddingTop: 40,
@@ -286,10 +287,10 @@ const styles = StyleSheet.create({
     marginTop: -30,
     borderTopLeftRadius: 20,
     borderTopRightRadius: 20,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   title: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 18,
     fontWeight: '700',
     paddingTop: 4,
@@ -302,12 +303,12 @@ const styles = StyleSheet.create({
   },
   inputLabel: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14
   },
   inputView: {
     flex: 2,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     borderRadius: 5,
     minHeight: 40,
@@ -345,12 +346,12 @@ const styles = StyleSheet.create({
     justifyContent: 'center'
   },
   sendBtnText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 13,
     fontWeight: 'bold'
   },
   divideText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     padding: 58,
     textAlign: 'center'

+ 398 - 358
Strides-APP/app/pages/sign/ResetPasswordV2.js

@@ -1,361 +1,401 @@
 /**
  * 忘记密码-重置密码页面V2
- * @邠心vbe on 2022/12/23
+ * @邠心vbe on 2023/02/02
  */
- import React, { Component } from 'react';
- import { View, Text, StyleSheet, ScrollView, Image, TextInput } from 'react-native';
- import apiUser from '../../api/apiUser';
- import Button from '../../components/Button';
- import Dialog from '../../components/Dialog';
- import { BackIcon } from '../../components/Toolbar';
- import { StrengthView } from './Register';
- 
- export default class ResetPassword extends Component {
-   constructor(props) {
-     super(props);
-     this.state = {
-       strength: 0,
-       password: '',
-       wrongCount: 0,
-       sendMinutes: 0
-     };
-     this.formInfo = {
-       email: '',
-       password: '',
-       verificationCode: ''
-     }
-   }
- 
-   componentWillUnmount() {
-     this.setState({
-       sendMinutes: 0
-     })
-   }
- 
-   getBackTopPosition() {
-     return isIOS ? statusHeight - 18 : 12;
-   }
- 
-   changeInfo(key, value) {
-     this.formInfo[key] = value;
-   }
- 
-   applyStrength(text) {
-     var strength = 0;
-     if (text.length >= 8) {
-       strength += 1;
-     }
-     if (/\d{1,}/.test(text)) {
-       strength += 1;
-     }
-     if (/[A-z]{1,}/.test(text)) {
-       strength += 1;
-     }
-     /*if (/[A-Z]{1,}/.test(text)) {
-       strength += 1;
-     }
-     if (/\W{1,}/.test(text)) {
-       strength += 1;
-     }*/
-     if (this.state.strength != strength) {
-       this.setState({
-         password: text,
-         strength: strength
-       });
-     } else {
-       this.setState({
-         password: text
-       });
-     }
-   }
- 
-   sendVerification() {
-     var info = this.formInfo;
-     if (!info.email) {
-       toastShort('Please enter email address');
-       return;
-     }
-     if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
-       toastShort('Email is incorrect format');
-       return;
-     }
-     Dialog.showProgressDialog()
-     apiUser.sendVerificationCode(info.email).then(res => {
-       Dialog.dismissLoading()
-       this.state.sendMinutes = 60;
-       toastShort('Send verification code successfully');
-       this.contdownTime();
-     }).catch(err => {
-       Dialog.dismissLoading()
-       toastShort(err);
-     });
-   }
- 
-   contdownTime() {
-     if (this.state.sendMinutes > 0) {
-       this.setState({
-         sendMinutes: this.state.sendMinutes - 1
-       })
-       setTimeout(() => {
-         this.contdownTime();
-       }, 1000);
-     }
-   }
- 
-   onResetPassword() {
-     var info = this.formInfo;
-     console.log('reset info', info);
-     if (!info.email) {
-       toastShort('Please enter email address');
-       return;
-     }
-     if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
-       toastShort('Email is incorrect format');
-       return;
-     }
-     if (!this.state.password) {
-       toastShort('Please enter password');
-       return;
-     }
-     if (this.state.strength < 3) {
-       toastShort('Password is not strong');
-       return;
-     }
-     if (!info.password) {
-       toastShort('Please enter confirm password');
-       return;
-     }
-     if (info.password != this.state.password) {
-       toastShort('The twice passwords are inconsistent');
-       return;
-     }
-     if (!info.verificationCode) {
-       toastShort('Please enter verification code');
-       return;
-     }
-     Dialog.showProgressDialog()
-     apiUser.updatePassword(this.formInfo).then(res => {
-       Dialog.dismissLoading()
-       toastShort('Reset password successfully');
-       goBack();
-     }).catch(err => {
-       Dialog.dismissLoading()
-       toastShort(err);
-     });
-   }
- 
-   render() {
-     return (
-       <View style={StyleSheet.absoluteFillObject}>
-         <ScrollView
-           style={styles.scollView}
-           keyboardShouldPersistTaps={'handled'}>
-           <View style={styles.header}>
-             <View style={styles.logoView}>
-               {/* <Image
-                 style={styles.logoImg}
-                 source={require('../../images/tool-logo.png')}/> */}
-             </View>
-           </View>
-           <View style={{...styles.backView, top: this.getBackTopPosition()}}>
-             <Button
-               style={styles.backButton}
-               viewStyle={styles.backButtonView}
-               onClick={() => goBack()}>
-               <BackIcon/>
-               <Text style={{color: '#333',fontSize: 16,paddingLeft: 8}}>Back to Login</Text>
-             </Button>
-           </View>
-           <View style={styles.resetView}>
-             <Text style={styles.title}>Reset Password</Text>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Email Address</Text>
-               <TextInput
-                 style={styles.inputView}
-                 placeholder='Email Address'
-                 maxLength={50}
-                 onChangeText={v => this.changeInfo('email', v)}
-               />
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>New Password</Text>
-               <TextInput
-                 secureTextEntry={this.state.wrongCount < 3}
-                 style={styles.inputView}
-                 placeholder='Password'
-                 maxLength={20}
-                 onChangeText={(value) => {
-                   this.applyStrength(value);
-                 }}
-               />
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}></Text>
-               <View style={styles.passwordView}>
-                 <View style={styles.strengthView}>
-                   <Text style={{fontSize:12, paddingRight: 4, color: '#333'}}>Password Strength</Text>
-                   <StrengthView {...this.state}/>
-                 </View>
-                 <View>
-                   <Text style={styles.passwordRole}>Your Password Must Have:</Text>
-                   <Text style={styles.passwordRole}>- 8 or more characters</Text>
-                   {/* <Text style={styles.passwordRole}>- upper and lower case letters</Text> */}
-                   <Text style={styles.passwordRole}>- at least one number</Text>
-                 </View>
-               </View>
-             </View>
-             <View style={styles.signInput}>
-               <Text style={styles.inputLabel}>Confirm Password</Text>
-               <TextInput
-                 secureTextEntry={this.state.wrongCount < 3}
-                 style={styles.inputView}
-                 placeholder='Password'
-                 maxLength={20}
-                 onChangeText={v => this.changeInfo('password', v)}
-               />
-             </View>
-             <View style={styles.signInput}>
-               <Text style={[styles.inputLabel, ui.flex2]}>Verification Code</Text>
-               <TextInput
-                 style={[styles.inputView, {flex: 2.6, marginLeft: 2}]}
-                 placeholder='Verification Code'
-                 maxLength={6}
-                 onChangeText={v => this.changeInfo('verificationCode', v)}
-               />
-               <Button
-                 text={this.state.sendMinutes > 0 ? this.state.sendMinutes : 'Get Code'}
-                 style={styles.sendBtn}
-                 disabled={this.state.sendMinutes > 0}
-                 viewStyle={styles.sendBtnView}
-                 textStyle={styles.sendBtnText}
-                 onClick={() => this.sendVerification()}
-               />
-             </View>
-           </View>
-           <Text style={styles.divideText}>We will send you an email with the verification code.</Text>
-           <Button
-             text='Reset Password'
-             style={styles.resetButton}
-             onClick={() => this.onResetPassword()}
-           />
-         </ScrollView>
-       </View>
-     );
-   }
- }
- 
- const styles = StyleSheet.create({
-   header: {
-     paddingTop: 56,
-     backgroundColor: colorThemes
-   },
-   backView: {
-     top: 12,
-     zIndex: 5,
-     padding: 16,
-     position: 'absolute',
-     flexDirection: 'row'
-   },
-   backButton: {
-     borderRadius: 60,
-     backgroundColor: 'white'
-   },
-   backButtonView: {
-     height: 43,
-     paddingLeft: 16,
-     paddingRight: 16,
-     alignItems: 'center',
-     flexDirection: 'row'
-   },
-   scollView: {
-     flex: 1,
-     backgroundColor: 'white'
-   },
-   logoView: {
-     paddingTop: 40,
-     paddingBottom: 56,
-     alignItems: 'center'
-   },
-   logoImg: {
-     width:165.09,
-     height: 51.94,
-   },
-   resetView: {
-     flex: 1,
-     padding: 16,
-     marginTop: -30,
-     borderTopLeftRadius: 20,
-     borderTopRightRadius: 20,
-     backgroundColor: 'white'
-   },
-   title: {
-     color: '#333',
-     fontSize: 18,
-     fontWeight: '700',
-     paddingTop: 4,
-     paddingBottom: 6,
-   },
-   signInput: {
-     marginTop: 16,
-     alignItems: 'center',
-     flexDirection: 'row'
-   },
-   inputLabel: {
-     flex: 1,
-     color: '#333',
-     fontSize: 14
-   },
-   inputView: {
-     flex: 2,
-     color: '#333',
-     fontSize: 14,
-     borderRadius: 5,
-     minHeight: 40,
-     paddingTop: 6,
-     paddingLeft: 12,
-     paddingRight: 12,
-     paddingBottom: 6,
-     backgroundColor: '#F5F5F5'
-   },
-   passwordView: {
-     flex: 2,
-     marginTop: -8,
-   },
-   strengthView: {
-     marginTop: -4,
-     alignItems: 'center',
-     paddingBottom: 4,
-     flexDirection: 'row'
-   },
-   passwordRole: {
-     color: '#999',
-     fontSize: 10,
-     lineHeight: 13
-   },
-   sendBtn: {
-     flex: 1.4,
-     marginLeft: 6
-   },
-   sendBtnView: {
-     flex: 1,
-     height: 40,
-     paddingLeft: 4,
-     paddingRight: 4,
-     alignItems: 'center',
-     justifyContent: 'center'
-   },
-   sendBtnText: {
-     color: '#fff',
-     fontSize: 13,
-     fontWeight: 'bold'
-   },
-   divideText: {
-     color: '#333',
-     fontSize: 14,
-     padding: 58,
-     textAlign: 'center'
-   },
-   resetButton: {
-     margin: 16
-   }
- })
+import React, { Component } from 'react';
+import { View, StyleSheet, ScrollView, Image, TextInput, Pressable } from 'react-native';
+import apiUser from '../../api/apiUser';
+import Button from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import { Styles } from '../../components/Toolbar';
+import StrengthView from './StrengthView';
+
+export default class ResetPassword extends Component {
+  constructor(props) {
+    super(props);
+    this.StrengthView = StrengthView.V2
+    this.strengthColor = ["#F5F5F5", "#F7C4CD", "#F2F8AC", "#F8DBAC", "#ACF8F6", "#A6E782"]
+    this.state = {
+      strength: 0,
+      password: '',
+      wrongCount: true,
+      sendMinutes: 0,
+      confirmStatusColor: "#F5F5F5"
+    };
+    this.formInfo = {
+      email: '',
+      password: '',
+      verificationCode: ''
+    }
+  }
+
+  componentWillUnmount() {
+    this.setState({
+      sendMinutes: 0
+    })
+  }
+
+  getBackTopPosition() {
+    return isIOS ? statusHeight - 18 : 12;
+  }
+
+  getConfirmStatusColor() {
+    var color = "#F5F5F5";
+    if (this.formInfo.password) {
+      if (this.state.password == this.formInfo.password) {
+        color = "#A6E782";
+      } else {
+        color = "#FA7B7B"
+      }
+    }
+    this.setState({
+      confirmStatusColor: color
+    })
+  }
+
+  changeInfo(key, value) {
+    this.formInfo[key] = value;
+    if (key == "password") {
+      this.getConfirmStatusColor();
+    }
+  }
+
+  applyStrength(text) {
+    this.StrengthView.apply(text, strength => {
+      if (this.state.strength != strength) {
+        this.setState({
+          password: text,
+          strength: strength
+        });
+      } else {
+        this.setState({
+          password: text
+        });
+      }
+      setTimeout(() => this.getConfirmStatusColor(), 300);
+    });
+  }
+
+  sendVerification() {
+    var info = this.formInfo;
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    Dialog.showProgressDialog()
+    apiUser.sendVerificationCode(info.email).then(res => {
+      Dialog.dismissLoading()
+      this.state.sendMinutes = res.data?.resendTime ?? 60;
+      toastShort('Send verification code successfully');
+      this.contdownTime();
+    }).catch(err => {
+      Dialog.dismissLoading()
+      toastShort(err);
+    });
+  }
+
+  contdownTime() {
+    if (this.state.sendMinutes > 0) {
+      this.setState({
+        sendMinutes: this.state.sendMinutes - 1
+      })
+      setTimeout(() => {
+        this.contdownTime();
+      }, 1000);
+    }
+  }
+
+  onResetPassword() {
+    var info = this.formInfo;
+    console.log('reset info', info);
+    if (!info.email) {
+      toastShort('Please enter email address');
+      return;
+    }
+    if (!/^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/.test(info.email)) {
+      toastShort('Email is incorrect format');
+      return;
+    }
+    if (!this.state.password) {
+      toastShort('Please enter password');
+      return;
+    }
+    if (this.state.strength < this.StrengthView.maxStrength) {
+      toastShort('Password is not strong');
+      return;
+    }
+    if (!info.password) {
+      toastShort('Please enter confirm password');
+      return;
+    }
+    if (info.password != this.state.password) {
+      toastShort('The twice passwords are inconsistent');
+      return;
+    }
+    if (!info.verificationCode) {
+      toastShort('Please enter verification code');
+      return;
+    }
+    Dialog.showProgressDialog()
+    apiUser.updatePassword(this.formInfo).then(res => {
+      Dialog.dismissLoading()
+      toastShort('Reset password successfully');
+      goBack();
+    }).catch(err => {
+      Dialog.dismissLoading()
+      toastShort(err);
+    });
+  }
+
+  getStrengthStyle() {
+    const persent = ((this.StrengthView.maxStrength - this.state.strength) / this.StrengthView.maxStrength) * 100
+    return {
+      left: 0,
+      right: 0,
+      height: 1,
+      bottom: -1,
+      marginRight: persent + "%",
+      position: 'absolute',
+      backgroundColor: "#A6E782"
+    }
+  }
+
+  changeSecurety() {
+    this.setState({
+      wrongCount: !this.state.wrongCount
+    })
+  }
+
+  render() {
+    return (
+      <View style={StyleSheet.absoluteFillObject}>
+        <ScrollView
+          style={styles.scollView}
+          keyboardShouldPersistTaps={'handled'}>
+          <View style={styles.resetView}>
+            {/* <Text style={styles.title}>Reset Password</Text> */}
+            <View style={styles.signInput}>
+              {/* <Text style={styles.inputLabel}>Email Address</Text> */}
+              <Image 
+                style={styles.inputIcon}
+                source={require('../../images/user/sign-email.png')}
+              />
+              <TextInput
+                style={styles.inputView}
+                placeholder='Email Address'
+                maxLength={50}
+                keyboardType="email-address"
+                textContentType='emailAddress'
+                clearButtonMode='while-editing'
+                onChangeText={v => this.changeInfo('email', v)}
+              />
+            </View>
+            <View style={styles.signInput}>
+              {/* <Text style={[styles.inputLabel, ui.flex2]}>Verification Code</Text> */}
+              <Image 
+                style={styles.inputIcon}
+                source={require('../../images/user/sign-otp.png')}
+              />
+              <TextInput
+                style={[styles.inputView, {flex: 2.6, marginLeft: 2}]}
+                placeholder='OTP'
+                maxLength={6}
+                keyboardType="number-pad"
+                textContentType="telephoneNumber"
+                onChangeText={v => this.changeInfo('verificationCode', v)}
+              />
+              <Button
+                text={this.state.sendMinutes > 0 ? (this.state.sendMinutes + " s") : 'EMAIL OTP'}
+                style={styles.sendBtn}
+                disabled={this.state.sendMinutes > 0}
+                viewStyle={styles.sendBtnView}
+                textStyle={styles.sendBtnText}
+                onClick={() => this.sendVerification()}
+              />
+            </View>
+            <View style={styles.signInput}>
+              {/* <Text style={styles.inputLabel}>New Password</Text> */}
+              <Image 
+                style={styles.inputIcon}
+                source={require('../../images/user/sign-password.png')}
+              />
+              <TextInput
+                secureTextEntry={this.state.wrongCount}
+                style={styles.inputView}
+                placeholder='New Password'
+                maxLength={20}
+                onChangeText={(value) => this.applyStrength(value)}
+              />
+              <Pressable
+                style={[Styles.backIcon, {position: 'absolute', right: 0}]}
+                android_ripple={rippleLess}
+                onPress={() => this.changeSecurety()}>
+                <MaterialCommunityIcons
+                  name={this.state.wrongCount ? "eye-off" : "eye"}
+                  size={20}
+                  color={textCancel}/>
+              </Pressable>
+              <View style={this.getStrengthStyle()}></View>
+            </View>
+            {/* <View style={styles.signInput}>
+              <Text style={styles.inputLabel}></Text>
+              <View style={styles.passwordView}>
+                <View style={styles.strengthView}>
+                  <Text style={{fontSize:12, paddingRight: 4, color: textPrimary}}>Password Strength</Text>
+                  <StrengthView {...this.state}/>
+                </View>
+                <View>
+                  <Text style={styles.passwordRole}>Your Password Must Have:</Text>
+                  <Text style={styles.passwordRole}>- 8 or more characters</Text>
+                  {/* <Text style={styles.passwordRole}>- upper and lower case letters</Text> *}
+                  <Text style={styles.passwordRole}>- at least one number</Text>
+                </View>
+              </View>
+            </View> */}
+            <View style={[styles.signInput, {borderBottomColor: this.state.confirmStatusColor}]}>
+              {/* <Text style={styles.inputLabel}>Confirm Password</Text> */}
+              <Image 
+                style={styles.inputIcon}
+                source={require('../../images/user/sign-password.png')}
+              />
+              <TextInput
+                secureTextEntry={this.state.wrongCount}
+                style={styles.inputView}
+                placeholder='Confirm Password'
+                maxLength={20}
+                onChangeText={v => this.changeInfo('password', v)}
+              />
+            </View>
+            <Button
+              text='Confirm'
+              style={styles.resetButton}
+              onClick={() => this.onResetPassword()}
+            />
+          </View>
+          {/* <Text style={styles.divideText}>We will send you an email with the verification code.</Text> */}
+        </ScrollView>
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  header: {
+    paddingTop: 56,
+    backgroundColor: colorThemes
+  },
+  backView: {
+    top: 12,
+    zIndex: 5,
+    padding: 16,
+    position: 'absolute',
+    flexDirection: 'row'
+  },
+  backButton: {
+    borderRadius: 60,
+    backgroundColor: colorLight
+  },
+  backButtonView: {
+    height: 43,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  scollView: {
+    flex: 1,
+    backgroundColor: colorLight
+  },
+  logoView: {
+    paddingTop: 40,
+    paddingBottom: 56,
+    alignItems: 'center'
+  },
+  logoImg: {
+    width:165.09,
+    height: 51.94,
+  },
+  resetView: {
+    flex: 1,
+    padding: 16,
+    marginTop: -12,
+    borderTopLeftRadius: 20,
+    borderTopRightRadius: 20,
+    backgroundColor: colorLight
+  },
+  inputIcon: {
+    width: 24,
+    height: 24
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: '700',
+    paddingTop: 4,
+    paddingBottom: 6,
+  },
+  signInput: {
+    marginTop: 16,
+    ...$padding(6, 16),
+    alignItems: 'center',
+    flexDirection: 'row',
+    borderBottomWidth: 1,
+    borderBottomColor: '#F5F5F5'
+  },
+  inputLabel: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14
+  },
+  inputView: {
+    flex: 2,
+    color: textPrimary,
+    fontSize: 14,
+    borderRadius: 5,
+    minHeight: 40,
+    ...$padding(6, 16),
+    //backgroundColor: '#F5F5F5'
+  },
+  passwordView: {
+    flex: 2,
+    marginTop: -8,
+  },
+  passwordRole: {
+    color: '#999',
+    fontSize: 10,
+    lineHeight: 13
+  },
+  sendBtn: {
+    flex: 1.4,
+    marginLeft: 6,
+    marginRight: -6,
+    borderRadius: 6
+  },
+  sendBtnView: {
+    flex: 1,
+    height: 40,
+    paddingLeft: 4,
+    paddingRight: 4,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  sendBtnText: {
+    color: colorLight,
+    fontSize: 13,
+    fontWeight: 'bold'
+  },
+  divideText: {
+    color: textPrimary,
+    fontSize: 14,
+    padding: 58,
+    textAlign: 'center'
+  },
+  resetButton: {
+    marginTop: 32,
+    marginBottom: 16
+  }
+})

+ 139 - 0
Strides-APP/app/pages/sign/StrengthView.js

@@ -0,0 +1,139 @@
+/**
+ * 密码强度验证组件
+ * @邠心vbe on 2023/02/01
+ */
+import React from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+
+const getStyle = (strength, num) => {
+  if (strength >= num) {
+    return {...styles.strengthItem, backgroundColor: colorAccent};
+  } else {
+    return styles.strengthItem;
+  }
+}
+
+const applyStrength = (text, version, back) => {
+  var strength = 0;
+  if (text.length >= 8) {
+    strength += 1;
+  }
+  if (/\d{1,}/.test(text)) {
+    strength += 1;
+  }
+  if (version == 1 || version == 2) {
+    if (/[a-z]{1,}/.test(text)) {
+      strength += 1;
+    }
+    if (/[A-Z]{1,}/.test(text)) {
+      strength += 1;
+    }
+  } else {
+    if (/[A-z]{1,}/.test(text)) {
+      strength += 1;
+    }
+  }
+  if (version == 1 || version == 3) {
+    if (/\W{1,}/.test(text)) {
+      strength += 1;
+    }
+  }
+  back(strength);
+}
+
+/**
+ * 密码强度组件
+ * @param {*} param0 动态强度
+ * @returns 组件
+ */
+const StrengthView = ({version, strength}) => (
+  <View>
+    <View style={styles.strengthView}>
+      <Text style={styles.labelText}>Password Strength</Text>
+      <>
+        <Text style={getStyle(strength, 1)}></Text>
+        <Text style={getStyle(strength, 2)}></Text>
+        <Text style={getStyle(strength, 3)}></Text>
+        { version < 4 && <>
+          <Text style={getStyle(strength, 4)}></Text>
+          { version < 2 &&
+            <Text style={getStyle(strength, 5)}></Text>
+          }
+        </>
+        }
+      </>
+    </View>
+    <View>
+      <Text style={styles.passwordRole}>Your Password Must Have:</Text>
+      <Text style={styles.passwordRole}>- 8 or more characters</Text>
+      { (version == 1 || version == 2) &&
+        <Text style={styles.passwordRole}>- upper and lower case letters</Text>
+      }
+      { (version == 1 || version == 3)
+      ? <Text style={styles.passwordRole}>- at least one number and one special character</Text>
+      : <Text style={styles.passwordRole}>- at least one number</Text>
+      }
+    </View>
+  </View>
+)
+
+export default {
+  /**
+   * 最严格的密码强度组件(5)
+   */
+  V1: {
+    VIEW: (props) => <StrengthView version={1} {...props}/>,
+    maxStrength: 5,
+    apply: (text, back) => applyStrength(text, 1, back)
+  },
+  /**
+   * 去掉特殊符号限制的密码强度组件(4)
+   */
+  V2: {
+    VIEW: (props) => <StrengthView version={2} {...props}/>,
+    maxStrength: 4,
+    apply: (text, back) => applyStrength(text, 2, back)
+  },
+  /**
+   * 去掉大小写字母限制的密码强度组件(4)
+   */
+  V3: {
+    VIEW: (props) => <StrengthView version={3} {...props}/>,
+    maxStrength: 4,
+    apply: (text, back) => applyStrength(text, 3, back)
+  },
+  /**
+   * 去掉大小写字母和特殊符号限制的密码强度组件(3)
+   */
+  V4: {
+    VIEW: (props) => <StrengthView version={4} {...props}/>,
+    maxStrength: 3,
+    apply: (text, back) => applyStrength(text, 4, back)
+  },
+}
+
+const styles = StyleSheet.create({
+  strengthView: {
+    marginTop: -4,
+    alignItems: 'center',
+    paddingBottom: 4,
+    flexDirection: 'row'
+  },
+  labelText: {
+    fontSize:12,
+    paddingRight: 4, 
+    color: textPrimary
+  },
+  passwordRole: {
+    color: '#999',
+    fontSize: 10,
+    lineHeight: 13
+  },
+  strengthItem: {
+    width: 14,
+    height: 2.5,
+    marginLeft: 8,
+    borderRadius: 3,
+    backgroundColor: '#CCC'
+  },
+})

+ 13 - 11
Strides-APP/app/pages/wallet/History.js

@@ -78,7 +78,7 @@ export default class History extends Component {
           <Text style={styles.rangeText}>9th Aug to 12th Aug</Text>
           <MaterialIcons
             name='arrow-drop-down'
-            color='#333'
+            color={colorDark}
             size={28}/>
         </View> */}
         <View style={styles.listView}>
@@ -94,8 +94,9 @@ export default class History extends Component {
                   }}>
                   <Image
                     style={styles.iconType}
+                    resizeMode="contain"
                     source={item.amountSymbol == 'M' ? IconCharge : IconPayment}/>
-                  <View style={styles.itemContent}>
+                  <View style={[styles.itemContent, index > 0 && styles.divide]}>
                     <View style={ui.flex1}>
                       <Text style={styles.issueName}>{item.createTime + ': ' + (item.amountSymbol == 'M' ? item.siteName : 'Top Up ' + item.amount)}</Text>
                       <Text style={styles.issueDesc}>Transaction ID: {item.creditRecordPk}</Text>
@@ -126,12 +127,14 @@ const styles = StyleSheet.create({
   },
   listView: {
     marginTop: 16,
-    backgroundColor: 'white'
+    minHeight: $vh(30),
+    backgroundColor: colorLight
   },
   noResult: {
     color: '#999',
     fontSize: 14,
     padding: 20,
+    lineHeight: $vh(20),
     textAlign: 'center',
   },
   itemView: {
@@ -143,21 +146,20 @@ const styles = StyleSheet.create({
   itemContent: {
     flex: 1,
     marginLeft: 16,
-    paddingTop: 16,
-    paddingLeft: 2,
-    paddingRight: 2,
-    paddingBottom: 16,
-    borderBottomWidth: 1,
-    borderBottomColor: '#eee',
+    ...$padding(16, 4),
     alignItems: 'center',
     flexDirection: 'row'
   },
+  divide: {
+    borderTopWidth: 1,
+    borderTopColor: '#eee',
+  },
   iconType: {
     width: 28,
     height: 28
   },
   issueName: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 12,
     paddingBottom: 2
   },
@@ -170,7 +172,7 @@ const styles = StyleSheet.create({
     fontSize: 14
   },
   amountText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14
   }
 })

+ 178 - 72
Strides-APP/app/pages/wallet/Overview.js

@@ -5,9 +5,9 @@
 import React, { Component } from 'react';
 import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
 import { ElevationObject } from '../../components/Button';
-import {Bar, VictoryAxis, VictoryBar, VictoryChart, VictoryTheme} from 'victory-native';
+import {VictoryAxis, VictoryBar, VictoryArea, VictoryChart, VictoryTheme, VictoryLabel} from 'victory-native';
 import apiWallet from '../../api/apiWallet';
-import Svg from 'react-native-svg';
+import Svg, { Defs, LinearGradient, Stop } from 'react-native-svg';
 import utils from '../../utils/utils';
 
 const chartThemes = "#6672B8"; //配置柱状图颜色
@@ -20,10 +20,12 @@ export default class Overview extends Component {
       monthData: [],
       weekdayData: [],
       weekIndex: 0,
-      monthIndex: 0
+      monthIndex: 0,
+      chartReady: false,
     };
     this.nowDataString = new Date().toDateString()
     this.refreshing = false;
+    this.enableChartArea = true;
   }
 
   componentDidMount() {
@@ -31,9 +33,21 @@ export default class Overview extends Component {
   }
 
   componentDidUpdate() {
-    if (this.props.refresh && !this.refreshing && this.props.shown) {
-      this.refreshing = true;
-      this.getOverview();
+    if (this.props.shown) {
+      if (this.props.refresh && !this.refreshing) {
+        this.refreshing = true;
+        this.getOverview();
+      } else if (!this.state.chartReady) {
+        setTimeout(() => {
+          this.setState({
+            chartReady: true
+          })
+        }, 100);
+      }
+    } else if (this.state.chartReady) {
+      this.setState({
+        chartReady: false
+      })
     }
   }
 
@@ -65,8 +79,17 @@ export default class Overview extends Component {
               monthIndex = index;
             }
             //item.y += index + 3;
+            item.y0 = 0;
             item.label = currency + item.y;
             item.title = item.x + ' | ' + item.label + ' | ' + item.power + 'kw';
+            if (this.enableChartArea) {
+              if (index == 0) {
+                item.label = "     " + currency + item.y
+              } else if (index == res.data.pastSixMonths.length - 1) {
+                item.label = currency + item.y + "     "
+              }
+              item.y += (item.y * 10 + 5);
+            }
             monthData.push(item);
           });
         }
@@ -76,7 +99,11 @@ export default class Overview extends Component {
         weekIndex: weekIndex,
         monthIndex: monthIndex,
         weekdayData: weekdayData,
-        monthData: monthData,
+        monthData: monthData
+      }, () => {
+        this.setState({
+          chartReady: true
+        })
       });
       this.stopRefresh();
     }).catch(err => {
@@ -95,7 +122,7 @@ export default class Overview extends Component {
   barTheme = (active) => ({
     data: { 
       fill: ({datum, index}) => {
-        return (datum.y > 0 ? (index == active ? colorPrimary : chartThemes) : "#999999");
+        return (datum.y > 0 ? (index == active ? colorAccent : chartThemes) : "#999999");
       }
     },
     labels: {
@@ -104,75 +131,143 @@ export default class Overview extends Component {
     }
   })
 
+  areaTheme = () => ({
+    data: {
+      //fill: chartThemes,
+      fill: chartThemes,
+      fillOpacity: 0.7,
+      stroke: colorPrimary,
+      strokeWidth: 1
+    },
+    labels: {
+      fontSize: 10,
+      fill: textSecondary
+    }
+  })
+
   getFill = ({datum, index}) => (datum.y > 0 ? (index == this.state.monthIndex ? colorPrimary : chartThemes) : "#999999");
 
   render() {
     return (
       <View style={this.props.shown ? ui.flex1 : styles.hide}>
-        <View style={styles.glanceView}>
-          <Text style={styles.glanceTitle}>At a glance</Text>
+        { this.props.atAglance &&
+          <View style={styles.glanceView}>
+            <Text style={styles.glanceTitle}>At a glance</Text>
+            <View style={styles.overviewRow}>
+              <View style={ui.flex1}>
+                <Text style={styles.valueText}>{this.state.glanceData.averageCharge ?? 0}</Text>
+                <Text style={styles.titleText}>kWh/Week</Text>
+                <Text style={styles.subTitleText}>Average Charge</Text>
+              </View>
+              <View style={ui.flex1}>
+                <Text style={styles.valueText}>{this.state.glanceData.averageSpend ?? 0}</Text>
+                <Text style={styles.titleText}>{currency}/Week</Text>
+                <Text style={styles.subTitleText}>Average Spend</Text>
+              </View>
+              <View style={ui.flex1}>
+                <Text style={styles.valueText}>{utils.hour2HHmm(this.state.glanceData.averageTime)}</Text>
+                <Text style={styles.titleText}>Hr/Week</Text>
+                <Text style={styles.subTitleText}>Average Time</Text>
+              </View>
+            </View>
+          </View>
+        }
+        <View style={styles.statisticView}>
+          <View style={ui.flexcw}>
+            <Text style={styles.sectionTitle}>For the week of</Text>
+            {/* <Text style={styles.linkText}>1st Jan to 8th Jan </Text> */}
+          </View>
           <View style={styles.overviewRow}>
             <View style={ui.flex1}>
-              <Text style={styles.valueText}>{this.state.glanceData.averageCharge ?? 0}</Text>
-              <Text style={styles.titleText}>kWh/Week</Text>
+              {/* <Text style={styles.valueText}>{this.state.glanceData.averageCharge ?? 0}</Text> */}
+              <Text style={styles.titleText}>{this.state.glanceData.averageCharge ?? 0} kWh/Week</Text>
               <Text style={styles.subTitleText}>Average Charge</Text>
             </View>
+            <View style={styles.overviewDivide}></View>
             <View style={ui.flex1}>
-              <Text style={styles.valueText}>{this.state.glanceData.averageSpend ?? 0}</Text>
-              <Text style={styles.titleText}>{currency}/Week</Text>
+              {/* <Text style={styles.valueText}>{this.state.glanceData.averageSpend ?? 0}</Text> */}
+              <Text style={styles.titleText}>{currency}{this.state.glanceData.averageSpend ?? 0}/Week</Text>
               <Text style={styles.subTitleText}>Average Spend</Text>
             </View>
+            <View style={styles.overviewDivide}></View>
             <View style={ui.flex1}>
-              <Text style={styles.valueText}>{utils.hour2HHmm(this.state.glanceData.averageTime)}</Text>
-              <Text style={styles.titleText}>Hr/Week</Text>
+              {/* <Text style={styles.valueText}>{utils.hour2HHmm(this.state.glanceData.averageTime)}</Text> */}
+              <Text style={styles.titleText}>{utils.hour2HHmm(this.state.glanceData.averageTime)}/Week</Text>
               <Text style={styles.subTitleText}>Average Time</Text>
             </View>
           </View>
         </View>
-        { this.props.shown &&
-          <View style={ui.flex1}>
-            { this.state.weekdayData.length > 0 &&
-              <View style={styles.statisticView}>
-                <Text style={styles.sectionTitle}>Statistics for this week</Text>
-                <Text style={styles.statisticTitle}>{this.state.weekdayData[this.state.weekIndex].title}</Text>
-                <Svg height={200}>
+        <View style={ui.flex1}>
+          <View style={styles.statisticView}>
+            <Text style={styles.sectionTitle}>Statistics for this week</Text>
+            <Text style={styles.statisticTitle}>{this.state.weekdayData[this.state.weekIndex]?.title}</Text>
+            <Svg height={200}>
+              { this.state.chartReady &&
+                <VictoryChart
+                  theme={VictoryTheme.material}
+                  animate={animate}
+                  height={200}
+                  padding={{top: 50, left: 32, right: 60, bottom: 50}}>
+                  <VictoryAxis style={axisTheme}/>
+                  <VictoryBar
+                    barWidth={25}
+                    style={this.barTheme(this.state.weekIndex)}
+                    data={this.state.weekdayData}
+                    events={[{
+                      target: "data",
+                      eventHandlers: {
+                        onPress: () => {
+                          return [{
+                            target: "data",
+                            mutation: (props) => {
+                              this.setState({
+                                weekIndex: props.index
+                              });
+                            }
+                          }];
+                        }
+                      }
+                    }]}
+                  />
+                </VictoryChart>
+              }
+            </Svg>
+          </View>
+          { this.state.monthData.length > 0 &&
+            <View style={styles.statisticView}>
+              <Text style={styles.sectionTitle}>Statistics for the past 6 months</Text>
+              <Text style={styles.statisticTitle}>{this.state.monthData[this.state.monthIndex].title}</Text>
+              {/* <svg style={{height: 0}}>
+                <defs>
+                  <linearGradient id="myGradient" gradientUnits="userSpaceOnUse">
+                    <stop stopColor={chartThemes} stopOpacity="0.8"/>
+                    <stop offset="1" stopColor={chartThemes} stopOpacity="0"/>
+                  </linearGradient>
+                </defs>
+              </svg> */}
+              { this.enableChartArea
+              ? <Svg height={200}>
+                { this.state.chartReady &&
                   <VictoryChart
                     theme={VictoryTheme.material}
-                    animate={animate}
-                    height={200}>
-                    <VictoryAxis style={axisTheme}/>
-                    <VictoryBar
-                      barWidth={25}
-                      style={this.barTheme(this.state.weekIndex)}
-                      data={this.state.weekdayData}
-                      events={[{
-                        target: "data",
-                        eventHandlers: {
-                          onPress: () => {
-                            return [{
-                              target: "data",
-                              mutation: (props) => {
-                                this.setState({
-                                  weekIndex: props.index
-                                });
-                              }
-                            }];
-                          }
-                        }
-                      }]}
-                    />
-                  </VictoryChart>
+                    height={200}
+                    padding={{top: 50, left: 30, right: 60, bottom: 50}}>
+                      <VictoryAxis style={axisTheme}/>
+                      <VictoryArea
+                        style={this.areaTheme()}
+                        data={this.state.monthData}
+                        theme={VictoryTheme.material}
+                        labels={({ datum }) => datum.y}
+                        labelComponent={<VictoryLabel dy={10}/>}
+                      />
+                    </VictoryChart>
+                  }
                 </Svg>
-              </View>
-            }
-            { this.state.monthData.length > 0 &&
-              <View style={styles.statisticView}>
-                <Text style={styles.sectionTitle}>Past 6 Months</Text>
-                <Text style={styles.statisticTitle}>{this.state.monthData[this.state.monthIndex].title}</Text>
-                <Svg height={200}>
-                <VictoryChart
-                  theme={VictoryTheme.material}
-                  height={200}>
+              : <Svg height={200}>
+                  <VictoryChart
+                    theme={VictoryTheme.material}
+                    height={200}
+                    padding={{top: 50, left: 30, right: 62, bottom: 50}}>
                     <VictoryAxis style={axisTheme}/>
                     <VictoryBar
                       barWidth={28}
@@ -196,10 +291,10 @@ export default class Overview extends Component {
                     />
                   </VictoryChart>
                 </Svg>
-              </View>
-            }
-          </View>
-        }
+              }
+            </View>
+          }
+        </View>
       </View>
     );
   }
@@ -232,10 +327,10 @@ const styles = StyleSheet.create({
     overflow: 'hidden',
     borderTopLeftRadius: 6,
     borderTopRightRadius: 6,  
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   glanceTitle: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 16,
     paddingTop: 4,
     paddingLeft: 16,
@@ -248,6 +343,11 @@ const styles = StyleSheet.create({
     alignItems: 'center',
     flexDirection: 'row'
   },
+  overviewDivide: {
+    width: 1,
+    height: 12,
+    backgroundColor: '#D9D9D9'
+  },
   valueText: {
     color: '#000',
     fontSize: 22,
@@ -255,27 +355,28 @@ const styles = StyleSheet.create({
     textAlign: 'center'
   },
   titleText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     textAlign: 'center'
   },
   subTitleText: {
-    color: '#999',
-    fontSize: 11,
+    color: textCancel,
+    fontSize: 12,
     textAlign: 'center'
   },
   statisticView: {
-    marginTop: 16,
+    marginTop: 0,
     paddingTop: 16,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   sectionTitle: {
-    color: '#333',
-    fontSize: 16,
-    paddingLeft: 16
+    color: textPrimary,
+    fontSize: 14,
+    paddingLeft: 16,
+    fontWeight: 'bold'
   },
   statisticTitle: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingTop: 24,
     paddingBottom: 8,
@@ -283,5 +384,10 @@ const styles = StyleSheet.create({
   },
   statisticChart: {
     height: 120
+  },
+  linkText: {
+    ...ui.link,
+    fontSize: 14,
+    paddingRight: 16
   }
 })

+ 44 - 22
Strides-APP/app/pages/wallet/Payment.js

@@ -3,8 +3,8 @@
  * @邠心vbe on 2021/04/23
  */
 import React, { useEffect, useState } from 'react';
-import { View, Text, StyleSheet, Image } from 'react-native';
-import Button from '../../components/Button';
+import { View, Text, StyleSheet, Image, Pressable } from 'react-native';
+import Button, { ElevationObject } from '../../components/Button';
 import utils from '../../utils/utils';
 import { LowCreditDialog } from '../charge/InfoDialog';
 import { PageList } from '../Router';
@@ -61,10 +61,11 @@ const Payment = ({
         ? <View style={{paddingLeft: 16}}>
             <Image
               style={{
-                width: 51,
-                height: 16
+                width: 50,
+                height: 14
               }}
-              source={require('../../images/app-logo.png')}/>
+              resizeMode={'contain'}
+              source={require('../../images/tool-logo.png')}/>
             <Text
               style={styles.rateText}
               numberOfLines={1}
@@ -100,7 +101,7 @@ const Payment = ({
         onClose={topup => {
           showDialog(false)
           if (topup) {
-            startPage(PageList.topup)
+            toTopupPage()
           } else {
             //goBack();
           }
@@ -112,15 +113,22 @@ const Payment = ({
 
 export default Payment;
 
-export const Balance = ({balance}) => {
+export const Balance = ({balance, action="View History", page=PageList.wallet}) => {
   return (
-    <View style={styles.balanceView}>
+    <Pressable
+      style={styles.balanceView}
+      onPress={() => startPage(page)}>
       <Image
         style={styles.balanceIcon}
-        source={require('../../images/icon/draw-wallet.png')}/>
-      <Text style={styles.balanceTitle}>Credit Wallet</Text>
+        source={require('../../images/user/card-wallet.png')}/>
+      <Text style={styles.balanceTitle}>Credit Wallet:</Text>
       <Text style={styles.balanceValue}>{currency}{getBalance(utils.isNotEmpty(balance) ? balance : userInfo.credit)}</Text>
-    </View>
+      <Text style={styles.actionText}>{action}</Text>
+      <FontAwesome
+        size={20}
+        color={textCancel}
+        name='angle-right'/>
+    </Pressable>
   );
 }
 
@@ -129,12 +137,15 @@ const getBalance = (balance) => {
   return balance ? balance.toFixed(2) : '0.0'
 }
 
-export const toTopupPage = () => {
+/*export const toTopupPage = () => {
   if (PaymentDefault.is2c2p) {
     startPage(PageList.topupV2);
   } else {
     startPage(PageList.topup);
   }
+}*/
+export const toTopupPage = () => {
+  startPage(PageList.topupNew);
 }
 
 const styles = StyleSheet.create({
@@ -146,7 +157,8 @@ const styles = StyleSheet.create({
     paddingBottom: 12,
     alignItems: 'center',
     flexDirection: 'row',
-    backgroundColor: '#F5F5F5',
+    ...ElevationObject(5),
+    backgroundColor: colorLight,
     justifyContent: 'space-between'
   },
   walletIcon: {
@@ -154,16 +166,16 @@ const styles = StyleSheet.create({
     height: 36
   },
   creditText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
   },
   rateText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
   },
   balance: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 16,
     textAlign: 'right',
     fontWeight: 'bold',
@@ -175,15 +187,16 @@ const styles = StyleSheet.create({
     height: 30
   },
   balanceTitle: {
-    flex: 1,
     color: '#000',
     fontSize: 16,
-    paddingLeft: 12
+    paddingLeft: 12,
+    fontWeight: 'bold'
   },
   balanceValue: {
+    flex: 1,
     color: '#000',
     fontSize: 18,
-    paddingRight: 6
+    paddingLeft: 6
   },
   infoStatus: {
     paddingTop: 4,
@@ -194,11 +207,11 @@ const styles = StyleSheet.create({
   },
   selected: {
     color: colorAccent,
-    backgroundColor: '#333'
+    backgroundColor: colorDark
   },
   topupView: {
     borderRadius: 4,
-    backgroundColor: '#333'
+    backgroundColor: colorDark
   },
   topupText: {
     color: colorAccent,
@@ -206,8 +219,17 @@ const styles = StyleSheet.create({
     fontWeight: 'normal'
   },
   balanceView: {
+    margin: 16,
     padding: 16,
     alignItems: 'center',
-    flexDirection: 'row'
+    flexDirection: 'row',
+    borderRadius: 6,
+    ...ElevationObject(5),
+    backgroundColor: colorLight
+  },
+  actionText: {
+    color: textCancel,
+    fontSize: 12,
+    paddingRight: 6
   }
 });

+ 2 - 3
Strides-APP/app/pages/wallet/PaythodList.js

@@ -3,8 +3,7 @@
  * @邠心vbe on 2021/05/08
  */
 import React, { Component } from 'react';
-import { View, Text, StyleSheet, Image, Pressable } from 'react-native';
-import { onChange } from 'react-native-reanimated';
+import { View, Text, StyleSheet, Pressable } from 'react-native';
 import apiWallet from '../../api/apiWallet';
 import ChargeItemSelect from '../../icons/ChargeItemSelect';
 
@@ -111,7 +110,7 @@ const styles = StyleSheet.create({
   },
   paytypeText: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
   },
   selectIcon: {

+ 10 - 8
Strides-APP/app/pages/wallet/Topup.js

@@ -157,7 +157,7 @@ export default class Topup extends Component {
         </View>
         <View style={styles.buttonView}>
           <Button
-            text='Top Up'
+            text='TOP UP'
             elevation={1.5}
             onClick={() => {
               //startPage(PageList.paycard)
@@ -195,7 +195,7 @@ const styles = StyleSheet.create({
     padding: 16,
     borderRadius: 10,
     marginBottom: 16,
-    backgroundColor: 'white'
+    backgroundColor: colorLight
   },
   topupTitle: {
     alignItems: 'center',
@@ -208,12 +208,12 @@ const styles = StyleSheet.create({
     backgroundColor: colorPrimary
   },
   titleText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 16,
     paddingLeft: 10
   },
   subTitle: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     marginTop: 16,
     marginBottom: 16
@@ -238,13 +238,13 @@ const styles = StyleSheet.create({
     marginLeft: 20
   },
   selected: {
-    color: '#fff',
-    borderColor: '#333',
+    color: textLight,
+    borderColor: colorDark,
     backgroundColor: colorPrimary
   },
   autoView: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14,
     paddingRight: 32
   },
@@ -253,4 +253,6 @@ const styles = StyleSheet.create({
     marginTop: 0,
     marginBottom: 16
   }
-})
+})
+
+export const TopupStyle = styles;

+ 260 - 0
Strides-APP/app/pages/wallet/TopupNew.js

@@ -0,0 +1,260 @@
+/**
+ * 新版钱包充值页面
+ * @邠心vbe on 2023/02/02
+ */
+import React, { Component } from 'react';
+import { View, Text, ScrollView, StyleSheet, Switch } from 'react-native';
+import apiWallet from '../../api/apiWallet';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
+import Button, { ElevationObject } from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import { PageList } from '../Router';
+import { Balance, PaymentDefault } from './Payment';
+import TopupPaythod from './TopupPaythod';
+
+export default class TopupNew extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isAuto: false,
+      topupList: [],
+      selectIndex: 0,
+      payType: {},
+      balance: 0,
+    };
+  }
+
+  componentDidMount() {
+    this.props.navigation.addListener('focus', () => {
+      getUserInfo(info => {
+        this.setState({
+          balance: info.credit
+        })
+      }, true);
+    });
+    this.getTopupList();
+  }
+
+  getTopupList() {
+    Dialog.showProgressDialog();
+    apiWallet.getTopUpAmountList().then(res => {
+      Dialog.dismissLoading();
+      if (res.data.length > 0) {
+        this.setState({
+          topupList: res.data
+        });
+      }
+    }).catch(err => {
+      Dialog.dismissLoading();
+    })
+  }
+
+  /**
+   * 2C2P充值
+   */
+  topup2() {
+    const params = {
+      payAmount: this.state.topupList[this.state.selectIndex]?.key,
+    }
+    apiWallet.doPaymentV2(params).then(res => {
+      Dialog.dismissLoading();
+      if (res.data.webPaymentUrl) {
+        startPage(PageList.paymentWeb, { amount: params.payAmount, url: res.data.webPaymentUrl, type: 'Topup' });
+      } else {
+        toastShort('Error 0')
+      }
+    }).catch(err => {
+      Dialog.dismissLoading();
+      toastShort(err);
+    });
+  }
+
+  /**
+   * FOMO充值
+   */
+  topup() {
+    const params = {
+      payAmount: this.state.topupList[this.state.selectIndex]?.key,
+      fomoPayType: this.state.payType.fomoPayType
+    }
+    //console.log('params',params);
+    if (params.payAmount) {
+      if (params.fomoPayType == 'PAYNOW' || params.fomoPayType == 'GRABPAY') {
+        //PAYNOW支付
+        Dialog.showProgressDialog();
+        apiWallet.doPayment(params).then(res => {
+          Dialog.dismissLoading();
+          if (res.data.fomoId && res.data.qrCodeInBase64) {
+            startPage(PageList.paynow, { amount: params.payAmount, base64: res.data.qrCodeInBase64 });
+          } else if (res.data.url) {
+            startPage(PageList.paymentWeb, { amount: params.payAmount, url: res.data.url, type: 'Topup' });
+          } else {
+            toastShort('Error 0')
+          }
+        }).catch(err => {
+          Dialog.dismissLoading();
+          toastShort(err);
+        });
+      } else {
+        //信用卡支付
+        startPage(PageList.formCard, { amount: params.payAmount, payType: params.fomoPayType });
+      }
+    } else {
+      toastShort('Error 1')
+    }
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <View style={styles.headerView}>
+          <Balance balance={this.state.balance}/>
+        </View>
+        <View style={styles.contentView}>
+          <View style={styles.topupView}>
+            <WalletTitle>Choose Top Up Value</WalletTitle>
+            <View style={styles.topupItems}>
+              { this.state.topupList.map((item, index) => {
+                  return (
+                    <BadgeSelectItem
+                      key={index}
+                      style={[styles.topupItem, index > 0 && styles.right]}
+                      checked={index == this.state.selectIndex}
+                      onPress={() => {
+                        this.setState({
+                          selectIndex: index
+                        })
+                      }}>
+                      <Text style={[styles.topupText, index == this.state.selectIndex && {color: colorAccent}]}>{currency}{item.key}</Text>
+                    </BadgeSelectItem>
+                  );
+                })
+              }
+            </View>
+          </View>
+          { !PaymentDefault.is2c2p &&
+            <View style={styles.topupView}>
+              <WalletTitle>Choose Payment Type</WalletTitle>
+              <TopupPaythod
+                onChange={type => {
+                  this.setState({
+                    payType: type
+                  });
+                }}/>
+            </View>
+          }
+        </View>
+        <View style={ui.flex1}></View>
+        <View style={styles.buttonView}>
+          <Button
+            text='TOP UP'
+            elevation={1.5}
+            onClick={() => {
+              PaymentDefault.is2c2p 
+              ? this.topup2()
+              : this.topup()
+            }}/>
+        </View>
+      </View>
+    );
+  }
+}
+
+export const WalletTitle = ({children}) => {
+  return (
+    <View style={styles.topupTitle}>
+      {/* <Text style={styles.titleLeft}></Text> */}
+      <Text style={styles.titleText}>{children}</Text>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: pageBackground
+  },
+  headerView: {
+    paddingBottom: 76,
+    //backgroundColor: colorAccent
+  },
+  contentView: {
+    padding: 16,
+    marginTop: -88
+  },
+  topupView: {
+    marginBottom: 16
+  },
+  topupViewOld: {
+    padding: 16,
+    borderRadius: 10,
+    marginBottom: 16,
+    backgroundColor: colorLight
+  },
+  topupTitle: {
+    marginBottom: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  titleLeft: {
+    width: 4,
+    height: 15,
+    borderRadius: 16,
+    backgroundColor: colorPrimary
+  },
+  titleText: {
+    color: textPrimary,
+    fontSize: 16,
+    paddingLeft: 0,
+    fontWeight: 'bold',
+    textShadowOffset: {
+      width: 0,
+      height: 1
+    },
+    textShadowRadius: 4,
+    textShadowColor: "rgba(0, 0, 0, 0.25)",
+  },
+  subTitle: {
+    color: textPrimary,
+    fontSize: 14,
+    marginTop: 16,
+    marginBottom: 16
+  },
+  topupItems: {
+    paddingBottom: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  topupItem: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: colorLight,
+    ...ElevationObject(5)
+  },
+  topupText: {
+    height: 60,
+    fontSize: 18,
+    lineHeight: 60,
+    color: textPrimary
+  },
+  right: {
+    marginLeft: 16
+  },
+  selected: {
+    color: textLight,
+    borderColor: colorDark,
+    backgroundColor: colorPrimary
+  },
+  autoView: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    paddingRight: 32
+  },
+  buttonView: {
+    padding: 16,
+    marginTop: 0,
+    marginBottom: 16
+  }
+})

+ 176 - 0
Strides-APP/app/pages/wallet/TopupPaythod.js

@@ -0,0 +1,176 @@
+/**
+ * 钱包充值的支付方式列表
+ * @邠心vbe on 2023/02/02
+ */
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, Image } from 'react-native';
+import Svg, { Path } from 'react-native-svg';
+import apiWallet from '../../api/apiWallet';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
+import { ElevationObject } from '../../components/Button';
+
+export default class TopupPaythod extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      cIndex: 0,
+      payTypeList: []
+    };
+  }
+
+  componentDidMount() {
+    this.getPayType();
+  }
+
+  getPayType() {
+    apiWallet.getPayTypeList().then(res => {
+      if (res.data) {
+        this.setState({
+          payTypeList: res.data
+        });
+        this.onChange(0);
+      }
+    }).catch(err => {
+
+    });
+  }
+
+  changePayType(index) {
+    this.setState({
+      cIndex: index
+    });
+    this.onChange(index);
+  }
+
+  onChange(index) {
+    if (this.props.onChange) {
+      this.props.onChange(this.state.payTypeList[index]);
+    }
+  }
+
+  getSecuryNumber(number, pk) {
+    if (pk) {
+      const last = number.substring(number.length - 8);
+      var x = ''
+      for (let i = 0; i < number.length - 8; i++) {
+        x += 'X';
+      }
+      return x + last;
+    } else {
+      return " ";
+    }
+  }
+
+  render() {
+    return (
+      <View style={styles.listView}>
+        { this.state.payTypeList.map((item, index) => {
+            return (
+              <BadgeSelectItem
+                key={index}
+                style={[
+                  styles.paytypeView,
+                  index > 0 && styles.next
+                ]}
+                checked={index == this.state.cIndex}
+                onPress={() => this.changePayType(index)}>
+                <View style={$padding(12, 8, 8)}>
+                  <PaymentIcon method={item.fomoPayType} checked={index == this.state.cIndex}/>
+                </View>
+                <Text style={[styles.paytypeText, (index == this.state.cIndex && {color: colorAccent})]}>{item.payName}</Text>
+                <Text style={styles.cardText}>{this.getSecuryNumber(item.payName, item.cardPk)}</Text>
+              </BadgeSelectItem>
+            );
+          })
+        }
+      </View>
+    );
+  }
+}
+
+export const PaymentIcon = ({method="CARD", size=40, checked=false}) => {
+  switch (method) {
+    case "CARD": 
+      return (
+        /*<Svg width="40" height="40" viewBox="0 0 40 40">
+          <Path
+            fill={checked ? colorAccent : "#666666"}
+            d="M22.0532 16.1052C21.3736 16.8184 21 17.7567 21 18.7535V20.2465C21 21.2433 21.3736 22.1817 22.0532 22.8948C22.7327 23.608 23.6269 24 24.5768 24H37V15H24.5768C23.6269 15 22.7288 15.392 22.0532 16.1052ZM26.0392 17.8318C26.9175 17.8318 27.6289 18.5783 27.6289 19.5C27.6289 20.4217 26.9175 21.1682 26.0392 21.1682C25.161 21.1682 24.4496 20.4217 24.4496 19.5C24.4496 18.5783 25.161 17.8318 26.0392 17.8318Z"/>
+          <Path
+            fill={checked ? colorAccent : "#666666"}
+            d="M32.2342 4H6.7697C4.1403 4 2 6.16334 2 8.82988V30.1701C2 32.8327 4.13639 35 6.7697 35H32.2303C34.8597 35 37 32.8367 37 30.1701V25.7563H24.7686C21.7557 25.7563 19.2907 23.2601 19.2907 20.2092V18.7908C19.2907 15.7399 21.7557 13.2437 24.7686 13.2437H37V8.82988C37 6.16731 34.8597 4 32.2342 4Z" />
+        </Svg>*/
+        <MaterialCommunityIcons
+          name="credit-card-multiple"
+          color={checked ? colorAccent : "#666666"}
+          size={size}/>
+      );
+    case "PAYNOW":
+      return (
+        <Svg width={size} height={size} viewBox="0 0 40 40">
+          <Path
+            fill={checked ? colorAccent : "#666666"}
+            d="M4 18.2222H7.55556V21.7778H4V18.2222ZM18.2222 7.55556H21.7778V14.6667H18.2222V7.55556ZM14.6667 18.2222H21.7778V25.3333H18.2222V21.7778H14.6667V18.2222ZM25.3333 18.2222H28.8889V21.7778H32.4444V18.2222H36V21.7778H32.4444V25.3333H36V32.4444H32.4444V36H28.8889V32.4444H21.7778V36H18.2222V28.8889H25.3333V25.3333H28.8889V21.7778H25.3333V18.2222ZM32.4444 32.4444V25.3333H28.8889V32.4444H32.4444ZM25.3333 4H36V14.6667H25.3333V4ZM28.8889 7.55556V11.1111H32.4444V7.55556H28.8889ZM4 4H14.6667V14.6667H4V4ZM7.55556 7.55556V11.1111H11.1111V7.55556H7.55556ZM4 25.3333H14.6667V36H4V25.3333ZM7.55556 28.8889V32.4444H11.1111V28.8889H7.55556Z"/>
+        </Svg>
+      )
+    case "GRABPAY":
+      return (
+        <Image
+          style={{width: size, height: size}}
+          source={checked ? require('../../images/wallet/payment-grab-active.png') : require('../../images/wallet/payment-grab.png')}
+        />
+      );
+    case "WALLET":
+      return (
+        <Svg width={size} height={size} viewBox="0 0 40 40">
+          <Path
+            fill={checked ? colorAccent : "#666666"}
+            d="M22.0532 16.1052C21.3736 16.8184 21 17.7567 21 18.7535V20.2465C21 21.2433 21.3736 22.1817 22.0532 22.8948C22.7327 23.608 23.6269 24 24.5768 24H37V15H24.5768C23.6269 15 22.7288 15.392 22.0532 16.1052ZM26.0392 17.8318C26.9175 17.8318 27.6289 18.5783 27.6289 19.5C27.6289 20.4217 26.9175 21.1682 26.0392 21.1682C25.161 21.1682 24.4496 20.4217 24.4496 19.5C24.4496 18.5783 25.161 17.8318 26.0392 17.8318Z"/>
+          <Path
+            fill={checked ? colorAccent : "#666666"}
+            d="M32.2342 4H6.7697C4.1403 4 2 6.16334 2 8.82988V30.1701C2 32.8327 4.13639 35 6.7697 35H32.2303C34.8597 35 37 32.8367 37 30.1701V25.7563H24.7686C21.7557 25.7563 19.2907 23.2601 19.2907 20.2092V18.7908C19.2907 15.7399 21.7557 13.2437 24.7686 13.2437H37V8.82988C37 6.16731 34.8597 4 32.2342 4Z"/>
+        </Svg>
+      )
+  }
+}
+
+const styles = StyleSheet.create({
+  listView: {
+    flexWrap: 'wrap',
+    flexDirection: 'row'
+  },
+  paytypeView: {
+    flex: 1,
+    alignItems: 'center',
+    backgroundColor: colorLight,
+    ...ElevationObject(5)
+  },
+  selected: {
+    borderColor: colorPrimary
+  },
+  next: {
+    marginLeft: 16,
+  },
+  paytypeText: {
+    color: textPrimary,
+    fontSize: 14,
+    fontWeight: 'bold',
+    paddingBottom: 14
+  },
+  cardText: {
+    color: "#666",
+    fontSize: 8,
+    marginTop: -18,
+    paddingBottom: 4
+  },
+  selectIcon: {
+    width: 18,
+    height: 18,
+  },
+  accountLogo: {
+    width: 36,
+    height: 18,
+    marginRight: 32
+  },
+});
+ 

+ 3 - 86
Strides-APP/app/pages/wallet/TopupV2.js

@@ -9,6 +9,7 @@ import Button from '../../components/Button';
 import Dialog from '../../components/Dialog';
 import { PageList } from '../Router';
 import { Balance } from './Payment';
+import { TopupStyle, WalletTitle } from './Topup';
 
 export default class TopupV2 extends Component {
   constructor(props) {
@@ -154,7 +155,7 @@ export default class TopupV2 extends Component {
         </View>
         <View style={styles.buttonView}>
           <Button
-            text='Top Up'
+            text='TOP UP'
             elevation={1.5}
             onClick={() => {
               //startPage(PageList.paycard)
@@ -166,88 +167,4 @@ export default class TopupV2 extends Component {
   }
 }
 
-export const WalletTitle = ({children}) => {
-  return (
-    <View style={styles.topupTitle}>
-      <Text style={styles.titleLeft}></Text>
-      <Text style={styles.titleText}>{children}</Text>
-    </View>
-  );
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    backgroundColor: '#F5F5F5'
-  },
-  headerView: {
-    paddingBottom: 76,
-    //backgroundColor: colorAccent
-  },
-  contentView: {
-    padding: 16,
-    marginTop: -88
-  },
-  topupView: {
-    padding: 16,
-    borderRadius: 10,
-    marginBottom: 16,
-    backgroundColor: 'white'
-  },
-  topupTitle: {
-    alignItems: 'center',
-    flexDirection: 'row'
-  },
-  titleLeft: {
-    width: 4,
-    height: 15,
-    borderRadius: 16,
-    backgroundColor: colorPrimary
-  },
-  titleText: {
-    color: '#333',
-    fontSize: 16,
-    paddingLeft: 10
-  },
-  subTitle: {
-    color: '#333',
-    fontSize: 14,
-    marginTop: 16,
-    marginBottom: 16
-  },
-  topupItems: {
-    paddingBottom: 16,
-    alignItems: 'center',
-    flexDirection: 'row'
-  },
-  topupItem: {
-    flex: 1,
-    height: 60,
-    lineHeight: 60,
-    color: '#666',
-    fontSize: 24,
-    borderWidth: 1,
-    borderRadius: 4,
-    borderColor: '#999',
-    textAlign: 'center'
-  },
-  right: {
-    marginLeft: 20
-  },
-  selected: {
-    color: '#fff',
-    borderColor: '#333',
-    backgroundColor: colorPrimary
-  },
-  autoView: {
-    flex: 1,
-    color: '#333',
-    fontSize: 14,
-    paddingRight: 32
-  },
-  buttonView: {
-    padding: 16,
-    marginTop: 0,
-    marginBottom: 16
-  }
-})
+const styles = TopupStyle;

+ 94 - 60
Strides-APP/app/pages/wallet/Wallet.js

@@ -3,13 +3,13 @@
  * @邠心vbe on 2021/05/07
  */
 import React, { Component } from 'react';
-import { View, Text, StyleSheet, ScrollView, RefreshControl } from 'react-native';
-import Payment, { toTopupPage } from './Payment';
+import { View, Text, StyleSheet, ScrollView, RefreshControl, Image, Pressable } from 'react-native';
+import Payment, { Balance, toTopupPage } from './Payment';
 import { PageList } from '../Router';
 import History from './History';
 import Overview from './Overview';
-import { MyRefreshProps } from '../../components/MyRefreshControl';
-import Button from '../../components/Button';
+import { MyRefreshProps } from '../../components/ThemesConfig';
+import Button, { ElevationObject } from '../../components/Button';
 
 export default class Wallet extends Component {
   constructor(props) {
@@ -18,6 +18,8 @@ export default class Wallet extends Component {
       balance: 0,
       tabIndex: 0,
       refreshing: false,
+      tabWidth: 0,
+      tabHeight: 0,
     };
   }
 
@@ -52,6 +54,16 @@ export default class Wallet extends Component {
     });
   }
 
+  tabLayout({nativeEvent}) {
+    //console.log('tab layout\n\n\n\n\n', nativeEvent.layout);
+    if (nativeEvent.layout.width) {
+      this.setState({
+        tabWidth: nativeEvent.layout.width,
+        tabHeight: 0.171955 * nativeEvent.layout.width
+      })
+    }
+  }
+
   render() {
     return (
       <ScrollView
@@ -63,48 +75,55 @@ export default class Wallet extends Component {
             onRefresh={() => this.onRefresh()}
           />
         }>
-        <View style={styles.balanceView}>
+        {/* <View style={styles.balanceView}>
           <Payment 
             balance={this.state.balance}
             isPayPerUse={false}
             isWallet={true}
             payType={"Credit Wallet"}
             topup={() => toTopupPage()}/>
-        </View>
-        <View style={styles.tabView}>
-          <Button
-            style={[
-              styles.tab,
-              styles.left,
-              this.state.tabIndex == 0 && styles.active
-            ]}
-            viewStyle={{}}
-            borderRadius={0}
-            onClick={() => this.changeTab(0)}>
-            <Text
-              style={[
-              styles.tabText,
-              this.state.tabIndex == 0 && styles.active
-            ]}>Overview</Text>
-          </Button>
-          <Button
-            style={[
-              styles.tab,
-              styles.right,
-            ]}
-            viewStyle={{}}
-            borderRadius={0}
-            onClick={() => this.changeTab(1)}>
-            <Text
-              style={[
-              styles.tabText,
-              styles.rightText,
-              this.state.tabIndex == 1 && styles.active
-            ]}>History</Text>
-          </Button>
-        </View>
-        <View>
+        </View> */}
+        <Balance
+          balance={this.state.balance}
+          action="Top Up"
+          page={PageList.topupNew}
+        />
+        <View style={styles.contentView}>
+          <View style={styles.tabView} onLayout={props => this.tabLayout(props)}>
+            { this.state.tabIndex == 0
+            ? <Image
+                style={[styles.tabBackgroundLeft, {width: this.state.tabWidth + 1, height: this.state.tabHeight}]}
+                resizeMode="cover"
+                source={require('../../images/wallet/tab-left.png')}
+              />
+            : <Image
+                style={[styles.tabBackgroundRight, {width: this.state.tabWidth + 1, height: this.state.tabHeight}]}
+                resizeMode="cover"
+                source={require('../../images/wallet/tab-right.png')}
+              />
+            }
+            <Pressable
+              style={styles.tabItem}
+              onPress={() => this.changeTab(0)}>
+              <Text
+                style={[
+                styles.tabText,
+                this.state.tabIndex == 0 && styles.active
+              ]}>Overview</Text>
+            </Pressable>
+            <View style={{width: 16}}></View>
+            <Pressable
+              style={styles.tabItem}
+              onPress={() => this.changeTab(1)}>
+              <Text
+                style={[
+                styles.tabText,
+                this.state.tabIndex == 1 && styles.active
+              ]}>History</Text>
+            </Pressable>
+          </View>
           <Overview
+            atAglance={false}
             refresh={this.state.refreshing}
             refreshed={() => this.stopRefresh()}
             shown={this.state.tabIndex == 0}/>
@@ -128,42 +147,57 @@ const styles = StyleSheet.create({
     paddingLeft: 16,
     paddingRight: 16,
     paddingBottom: 12,
-    backgroundColor: '#fff'
+    backgroundColor: colorLight
   },
   tabView: {
+    height: 60,
     padding: 16,
+    overflow: 'hidden',
     alignItems: 'center',
     flexDirection: 'row',
     justifyContent: 'center'
   },
+  tabItem: {
+    flex: 1,
+    marginTop: -14,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
   tabText: {
-    color: '#333',
-    fontSize: 14,
+    color: textSecondary,
+    fontSize: 16,
     paddingTop: 5,
     paddingLeft: 36,
     paddingRight: 36,
     paddingBottom: 5,
   },
-  tab: {
-    borderWidth: 1,
-    borderColor: '#333',
-    overflow: 'hidden',
-    backgroundColor: '#fff'
-  },
-  left: {
-    borderTopLeftRadius: 6,
-    borderBottomLeftRadius: 6
+  active: {
+    color: textPrimary,
+    fontWeight: 'bold'
   },
-  right: {
-    borderTopRightRadius: 6,
-    borderBottomRightRadius: 6
+  contentView: {
+    ...$margin(8, 16, 16),
+    overflow: 'hidden',
+    borderRadius: 6,
+    ...ElevationObject(5),
+    backgroundColor: colorLight
   },
-  rightText: {
-    paddingLeft: 40,
-    paddingRight: 40,
+  tabBackgroundLeft: {
+    top: -1,
+    left: -1,
+    right: 0,
+    paddingRight: 1,
+    width: $vw(90),
+    height: $vw(14),
+    position: 'absolute'
   },
-  active: {
-    color: colorAccent,
-    backgroundColor: '#333'
+  tabBackgroundRight: {
+    top: -1,
+    left: 0,
+    right: -1,
+    paddingRight: 1,
+    width: $vw(90),
+    height: $vw(14),
+    position: 'absolute'
   }
 })

+ 10 - 21
Strides-APP/app/utils/constant.js

@@ -23,7 +23,7 @@ if (isIOS) {
   const {StatusBarManager} = NativeModules;
   StatusBarManager.getHeight(statusBarHeight => {
     const height = statusBarHeight.height;
-    global.toolbarSize = 56 + height;
+    global.toolbarSize = toolbarSize + height;
     global.statusHeight = height;
   });
   global.BRAND = '';
@@ -36,6 +36,7 @@ global.currency = '$';
 global.accessToken = '';
 global.startPage = {};
 global.storageSite = [];
+global.chargeInfoState = {};
 
 global.userInfo = {
   userPk: -1,
@@ -77,23 +78,21 @@ global.$vh = percent => {
 };
 
 global.$vhs = percent => {
-  return (global.$height - global.statusHeight) * percent / 100;
+  return global.$height * percent / 100 - global.statusHeight;
 };
 
 global.$vht = percent => {
-  return (global.$height - global.statusHeight) * percent / 100 - 56;
+  return global.$height * percent / 100 - toolbarSize;
+};
+
+global.$vhts = percent => {
+  return global.$height * percent / 100 - toolbarSize - global.statusHeight;
 };
 
 global.$vw = percent => {
   return (global.$width * percent) / 100;
 };
 
-//Theme
-global.colorThemes = '#FFFFFF';//#FFFFFF
-global.colorAccent = '#A3C93A';
-global.colorPrimary = '#001489';
-global.colorPrimaryDark = '#FFFFFF';
-
 global.$padding = (top, right, bottom, left) => {
   if (top == undefined) {
     return {};
@@ -161,7 +160,7 @@ global.$borderRadius = (topLeft, topRight, bottomRight, bottomLeft) => {
 global.ui = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: 'white'
+    backgroundColor: pageBackground
   },
   flex: {
     flexDirection: 'row'
@@ -242,7 +241,7 @@ global.ui = StyleSheet.create({
     backgroundColor: colorAccent
   },
   buttonText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 17,
     fontWeight: 'bold',
     textAlign: 'center',
@@ -255,16 +254,6 @@ global.ui = StyleSheet.create({
   }
 });
 
-global.ripple = {
-  color: 'rgba(0,0,0,.3)'
-}
-
-global.rippleLess = {
-  color: 'rgba(0,0,0,.3)',
-  radius: 20,
-  borderless: true
-}
-
 /*if (!__DEV__) {
   global.console = {
     info: () => {},

+ 61 - 0
Strides-APP/app/utils/themes.js

@@ -0,0 +1,61 @@
+import { Appearance } from "react-native";
+
+const enableDarkMode = false;//启用黑暗模式
+//theme
+global.pageTitleTint = '#000000';
+global.pageBackground = '#fafafa';
+global.colorThemes = '#FFFFFF';//#FFFFFF
+global.colorAccent = '#A3C93A';//56D905
+global.colorPrimary = '#001489';
+global.colorPrimaryDark = '#FFFFFF';//#FFFFFF
+//background
+global.colorDark = "#303030";
+global.colorLight = "#FFFEFE";
+global.colorCancel = "#CCCCCC";
+//text
+global.textPrimary = '#333333';
+global.textSecondary = '#555555';
+global.textGrey = "#CCCCCC";
+global.textButton = "#FFFFFF";//#222
+global.textCancel = "#999999";
+global.textDark = "#000000";
+global.textLight = "#FFFFFF";
+global.rippleColor = "rgba(0,0,0,.2)"
+
+global.darkMode = false;
+global.themeStatusBar = "dark-content"
+
+if (Appearance.getColorScheme() == 'dark' && enableDarkMode) {
+  global.darkMode = true;
+  global.themeStatusBar = "light-content"
+  
+  global.pageTitleTint = '#FFFFFF';
+  global.pageBackground = '#353535';
+  global.colorThemes = '#323232';
+  global.colorAccent = '#222222';
+  global.colorPrimary = '#000000';
+  global.colorPrimaryDark = "#000000";
+  global.colorLight = "#000000";
+  global.colorDark = "#FFFCF8";
+  global.textPrimary = '#ffffff';
+  global.textSecondary = '#E0E0E0';
+  global.textDark = "#FFFFFF";
+  global.textLight = "#000000";
+  global.rippleColor = "rgba(200,200,200,.3)"
+}
+
+global.ripple = {
+  color: rippleColor
+}
+
+global.rippleLess = {
+  color: rippleColor,
+  radius: 20,
+  borderless: true
+}
+
+global.rippleLessIcon = {
+  color: rippleColor,
+  radius: 22,
+  borderless: true
+}

+ 25 - 0
Strides-APP/app/utils/utils.js

@@ -40,6 +40,15 @@ export default {
   },
   getSiteInfo(obj) {
     if (obj) {
+      const acRates = [], dcRates = [];
+      obj?.rates.forEach((item) => {
+        if (item.type?.indexOf('AC') >= 0) {
+          acRates.push(item)
+        } else {
+          dcRates.push(item)
+        }
+      })
+      
       return {
         id: obj.sitePk,
         name: obj.siteName,
@@ -50,6 +59,8 @@ export default {
         allConnector: obj.allConnector,
         dcConnector: obj.dcConnector,
         distance: this.getDistance(obj.distance),
+        acRates: acRates,
+        dcRates: dcRates,
         rateList: obj.rates,
         siteType: obj.siteType,
         parkingFee: obj.parkingFee,
@@ -100,6 +111,20 @@ export default {
       return '0 min';
     }
   },
+  minutes2HHmm(minutes) {
+    if (minutes) {
+      if (minutes > 60) {
+        const m = parseInt(minutes);
+        const h = m / 60;
+        const mm = m % 60;
+        return h + ' hr ' + parseInt(mm) + 'min';
+      } else {
+        return parseInt(minutes) + 'min';
+      }
+    } else {
+      return '0 min';
+    }
+  },
   isEmpty(str, encNo=false) {
     if (typeof str == 'number') {
       if (encNo) {

+ 4 - 4
Strides-APP/index.js

@@ -4,8 +4,9 @@
 import React from 'react';
 import {AppRegistry, KeyboardAvoidingView, StatusBar} from 'react-native';
 import 'react-native-gesture-handler';
-import './app/utils/notification';
+import './app/utils/themes'
 import './app/utils/constant';
+import './app/utils/notification';
 import './app/utils/vector_icon';
 import Router from './app/pages/Router';
 import {name as appName} from './app.json';
@@ -14,11 +15,10 @@ import ModalPortal from './app/components/ModalPortal';
 import {RootSiblingParent} from 'react-native-root-siblings';
 import { SafeAreaView } from 'react-native-safe-area-context';
 
-
 const Index = () => {
   return (
     <RootSiblingParent>
-      <StatusBar barStyle="dark-content" backgroundColor={colorPrimaryDark}/>
+      <StatusBar barStyle={themeStatusBar} backgroundColor={colorPrimaryDark}/>
       { isIOS
         ? <KeyboardAvoidingView style={ui.flex1} behavior="padding">
             <Router />
@@ -26,7 +26,7 @@ const Index = () => {
         : <Router />
       }
       <ModalPortal />
-      <SafeAreaView style={{flex: 0, backgroundColor: 'white'}}></SafeAreaView>
+      <SafeAreaView style={{flex: 0, backgroundColor: colorLight}}></SafeAreaView>
     </RootSiblingParent>
   );
 };

+ 2 - 0
Strides-APP/package.json

@@ -26,6 +26,7 @@
     "@react-native-community/masked-view": "^0.1.10",
     "@react-native-community/push-notification-ios": "^1.8.0",
     "@react-navigation/drawer": "^5.12.5",
+    "@react-navigation/material-top-tabs": "^5.3.19",
     "@react-navigation/native": "^5.9.4",
     "@react-navigation/stack": "^5.14.4",
     "axios": "^0.21.1",
@@ -52,6 +53,7 @@
     "react-native-screens": "^3.1.1",
     "react-native-share": "^6.1.0",
     "react-native-svg": "^12.1.1",
+    "react-native-tab-view": "^2.16.0",
     "react-native-vector-icons": "^9.2.0",
     "react-native-view-shot": "^3.1.2",
     "react-native-webview": "^11.6.4",