Эх сурвалжийг харах

Implement pop up for pay-per-use to indicate disclaimer
https://dev.wormwood.com.sg/zentao/task-view-318.html

vbea 1 жил өмнө
parent
commit
dabd28203f

+ 5 - 3
Strides-APP/app/components/Button.js

@@ -16,7 +16,8 @@ export default Button = ({
   elevation = 0,
   borderRadius = 40,
   iconLeft,
-  iconRight
+  iconRight,
+  numberOfLines=1
 }) => {
   //var start = {}, end = {};
   //const isSamsung = BRAND == 'samsung';
@@ -65,7 +66,7 @@ export default Button = ({
         android_ripple={ripple}>
         {iconLeft ?? <></>}
         { children ? children :
-          <TextView style={mergeStyle(textStyle, textColor, textSize, disabled)}>{text}</TextView>
+          <TextView style={mergeStyle(textStyle, textColor, textSize, disabled)} numberOfLines={numberOfLines}>{text}</TextView>
         }
         {iconRight ?? <></>}
       </Pressable>
@@ -216,6 +217,7 @@ const styles = StyleSheet.create({
     color: textButton,
     fontSize: 16,
     fontWeight: 'bold',
-    textAlign: 'center'
+    textAlign: 'center',
+    textTransform: 'uppercase'
   }
 });

+ 1 - 0
Strides-APP/app/components/Dialog.js

@@ -377,6 +377,7 @@ const andStyles = StyleSheet.create({
 });
 
 export default Dialog = {
+  dialogWidth: maxWidth,
   BUTTON_OK: BUTTON_OK,
   BUTTON_CANCEL: BUTTON_CANCEL,
   isShowing: ModalPortal.isShowing,

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

@@ -31,7 +31,7 @@ export const ANIMATION_DIRECTION = {
 
 const VbeSkeleton = ({
   style=styles.container,
-  viewStyle=styles.viewStyle,
+  viewStyle={},
   textStyle={},
   text,
   duration=1200,

+ 2 - 2
Strides-APP/app/i18n/index.js

@@ -68,7 +68,7 @@ const LOCALES = [{
   value: "zh-HK",//繁體中文
   enable: true
 }]
-if (app.modules.i18n) {
+if (app.modules.i18n && !app.isLumiWhitelabel) {
   I18n.translations = {
     default: en,
     en, es, de, fr,
@@ -125,7 +125,7 @@ global.$t = (scope) => {
 }
 
 const init = (back) =>  {
-  if (app.modules.i18n) {
+  if (app.modules.i18n && !app.isLumiWhitelabel) {
     getStorage(SETTING_KEY).then(res => {
       if (res) {
         global.currentLocale = I18n.locale = res;

+ 3 - 1
Strides-APP/app/i18n/locales/en.js

@@ -438,7 +438,9 @@ export default {
     tipsPayPerUseCharge: "Once the payment is received, the charging will start automatically.",
     tipsPayPerUseSave: "Please save the QR Code, and use your bank/eWallet app to scan the QR Code.",
     tipsQrPayment: "Once the payment is completed, the top up amount will be credited into your credits automatically.",
-    tipsQrPaymentSave: "Please save the QR Code, and use your bank app to scan the QR Code."
+    tipsQrPaymentSave: "Please save the QR Code, and use your bank app to scan the QR Code.",
+    tipsPayPerUseAmount: "A pre-authorization amount of {mm} will be held. Upon completion of your charging session, the unused balance will be refunded. Please note that the timing of the refund depends on your bank’s policy. Thank you for your understanding.",
+    titleNote: "Note:"
   },
   settings: {
     autoRefreshInterval: "Auto refresh interval",

+ 3 - 1
Strides-APP/app/i18n/locales/zh-TW.js

@@ -438,7 +438,9 @@ export default {
     tipsPayPerUseCharge: "壹旦收到付款,將自動開始充電",
     tipsPayPerUseSave: "請儲存二維碼,並使用您的銀行/電子錢包應用程式掃描二維碼",
     tipsQrPayment: "付款完成後,充值金額將自動記入您的餘額",
-    tipsQrPaymentSave: "請儲存二維碼,並使用您的銀行應用程式掃描二維碼"
+    tipsQrPaymentSave: "請儲存二維碼,並使用您的銀行應用程式掃描二維碼",
+    tipsPayPerUseAmount: "將扣除{mm}的預授權金額。完成充電後,未使用的餘額將退還。請注意,退款的時間取決於您銀行的政策,謝謝您的理解。",
+    titleNote: "提示:"
   },
   settings: {
     autoRefreshInterval: "自動更新站點",

+ 3 - 1
Strides-APP/app/i18n/locales/zh.js

@@ -438,7 +438,9 @@ export default {
     tipsPayPerUseCharge: "一旦收到付款,将自动开始充电",
     tipsPayPerUseSave: "请保存二维码,并使用您的银行/电子钱包应用程序扫描二维码",
     tipsQrPayment: "付款完成后,充值金额将自动记入您的余额",
-    tipsQrPaymentSave: "请保存二维码,并使用您的银行应用程序扫描二维码"
+    tipsQrPaymentSave: "请保存二维码,并使用您的银行应用程序扫描二维码",
+    tipsPayPerUseAmount: "将扣除{mm}的预授权金额。完成充电后,未使用的余额将退还。请注意,退款的时间取决于您银行的政策。谢谢你的理解。",
+    titleNote: "提示:"
   },
   settings: {
     autoRefreshInterval: "自动更新站点",

+ 1 - 1
Strides-APP/app/pages/Settings.js

@@ -164,7 +164,7 @@ export default class Settings extends Component {
   render() {
     return (
       <View style={ui.flex1}>
-        { app.modules.i18n &&
+        { (app.modules.i18n && !app.isLumiWhitelabel) &&
           <View style={styles.menuView}>
             <TextView style={styles.buttonText}>{$t('settings.language')}</TextView>
             <Dropdown

+ 51 - 23
Strides-APP/app/pages/alert/ListAlerts.js

@@ -12,15 +12,18 @@ import { PageList } from '../Router';
 import AlertUtil from './AlertUtil';
 import ItemView from './ItemAlert';
 import TextView from '../../components/TextView';
+import VbeSkeleton from '../../components/VbeSkeleton';
 
 export default class ListAlerts extends Component {
   constructor(props) {
     super(props);
     this.state = {
+      loading: true,
       dataList: [],
       refreshing: false,
       showReadAll: false,
-      unreadTotal: 0
+      unreadTotal: 0,
+      loadingList: ["", "", "", ""]
     };
   }
 
@@ -90,9 +93,10 @@ export default class ListAlerts extends Component {
       toastShort(err)
     }).finally(() => {
       this.setState({
+        loading: false,
         refreshing: false
-      })
-    })
+      });
+    });
   }
 
   getMessageListPage() {
@@ -178,26 +182,46 @@ export default class ListAlerts extends Component {
   }
 
   render() {
-    return (
-      <FlatList
-        style={styles.listView}
-        data={this.state.dataList}
-        renderItem={this.listItem}
-        ListHeaderComponent={this.topView}
-        ItemSeparatorComponent={this.divideView}
-        keyExtractor={item => item.notificationId}
-        onEndReached={() => this.getMessageListPage()}
-        onEndReachedThreshold={0.3}
-        refreshControl={
-          <RefreshControl
-            {...MyRefreshProps()}
-            refreshing={this.state.refreshing}
-            onRefresh={() => this.onRefresh()}
-          />
-        }
-        ListEmptyComponent={<Text style={styles.noData}>{$t('notification.empty')}</Text>}
-      />
-    );
+    if (this.state.loading) {
+      return (
+        <View style={styles.listView}>
+          { this.state.loadingList.map((item, index) =>
+            <View style={styles.loadingView} key={index}>
+              <VbeSkeleton
+                style={ui.flex1}
+                layout={[
+                  {width: '90%', height: 18},
+                  {width: '50%', height: 12, marginTop: 6},
+                  {width: '100%', height: 15, marginTop: 8}
+                ]}
+                animationDirection={"horizontalRight"}
+              />
+            </View>
+          )}
+        </View>
+      )
+    } else {
+      return (
+        <FlatList
+          style={styles.listView}
+          data={this.state.dataList}
+          renderItem={this.listItem}
+          ListHeaderComponent={this.topView}
+          ItemSeparatorComponent={this.divideView}
+          keyExtractor={item => item.notificationId}
+          onEndReached={() => this.getMessageListPage()}
+          onEndReachedThreshold={0.3}
+          refreshControl={
+            <RefreshControl
+              {...MyRefreshProps()}
+              refreshing={this.state.refreshing}
+              onRefresh={() => this.onRefresh()}
+            />
+          }
+          ListEmptyComponent={<Text style={styles.noData}>{$t('notification.empty')}</Text>}
+        />
+      );
+    }
   }
 }
 
@@ -240,5 +264,9 @@ const styles = StyleSheet.create({
     color: textLight,
     fontSize: 12,
     textTransform: "uppercase"
+  },
+  loadingView: {
+    padding: 16,
+    flexDirection: 'row'
   }
 })

+ 32 - 9
Strides-APP/app/pages/alert/ViewAlerts.js

@@ -6,9 +6,9 @@ import React, { Component } from 'react';
 import { View, StyleSheet, ScrollView } from 'react-native';
 import apiNotification from '../../api/apiNotification';
 import Button, { ElevationObject } from '../../components/Button';
-import Dialog from '../../components/Dialog';
 import HeaderTitle from '../../components/HeaderTitle';
 import TextView from '../../components/TextView';
+import VbeSkeleton from '../../components/VbeSkeleton';
 import { PageList } from '../Router';
 
 export default class ViewAlerts extends Component {
@@ -16,6 +16,7 @@ export default class ViewAlerts extends Component {
     super(props);
     this.state = {
       id: "",
+      loading: true,
       messageInfo: {
         createTime: "",
         notificationText: "",
@@ -36,26 +37,29 @@ export default class ViewAlerts extends Component {
   }
 
   readMessage() {
-    Dialog.showProgressDialog();
     apiNotification.readMessage(this.state.id).then(res => {
       if (res.data) {
         this.setState({
           messageInfo: res.data
+        }, () => {
+          this.setPageTitle();
         });
-        this.setPageTitle();
       }
     }).catch(err => {
       toastShort(err);
-    }).finally(() => {
-      Dialog.dismissLoading();
-    })
+    });
   }
 
   setPageTitle() {
     if (this.state.messageInfo.notificationTitle) {
       this.props.navigation.setOptions({
         headerTitle: () => (<HeaderTitle title={this.state.messageInfo.notificationTitle}/>)
-      })
+      });
+      setTimeout(() => {
+        this.setState({
+          loading: false
+        });
+      }, 300);
     }
   }
 
@@ -65,7 +69,20 @@ export default class ViewAlerts extends Component {
 
   render() {
     return (
-      <View style={styles.container}>
+      <VbeSkeleton
+        style={this.state.loading ? styles.loadingView : styles.container}
+        viewStyle={styles.container}
+        isLoading={this.state.loading}
+        layout={[
+          {width: '90%', height: 20},
+          {width: '50%', height: 12, marginTop: 8},
+          {width: '100%', height: 15, marginTop: 24},
+          {width: '100%', height: 15, marginTop: 8},
+          {width: '100%', height: 15, marginTop: 8},
+          {width: '100%', height: 15, marginTop: 8},
+          {width: '30%', height: 15, marginTop: 8}
+        ]}
+        animationDirection={"horizontalRight"}>
         <View style={styles.header}>
           <TextView
             style={styles.textTitle}>
@@ -100,7 +117,7 @@ export default class ViewAlerts extends Component {
             onClick={() => this.submitFeedback()}
           />
         }
-      </View>
+      </VbeSkeleton>
     );
   }
 }
@@ -110,6 +127,12 @@ const styles = StyleSheet.create({
     flex: 1,
     backgroundColor: pageBackground
   },
+  loadingView: {
+    flex: 1,
+    padding: 16,
+    justifyContent: 'flex-start',
+    backgroundColor: pageBackground
+  },
   textTitle: {
     color: textPrimary,
     fontSize: 14,

+ 114 - 81
Strides-APP/app/pages/alert/ViewArticle.js

@@ -9,6 +9,7 @@ import apiArticle from '../../api/apiArticle';
 import { ElevationObject } from '../../components/Button';
 import HeaderTitle from '../../components/HeaderTitle';
 import TextView from '../../components/TextView';
+import VbeSkeleton from '../../components/VbeSkeleton';
 import utils from '../../utils/utils';
 import { PagerView } from './ViewUtil';
 
@@ -17,6 +18,7 @@ export default class ViewArticle extends Component {
     super(props);
     this.state = {
       id: "",
+      loading: true,
       messageInfo: {
         articleTypeName: "",
         articleTitle: "",
@@ -36,7 +38,6 @@ export default class ViewArticle extends Component {
   }
 
   readMessage() {
-    Dialog.showProgressDialog();
     apiArticle.readMessage(this.state.id).then(res => {
       if (res.data) {
         this.setState({
@@ -46,16 +47,19 @@ export default class ViewArticle extends Component {
       }
     }).catch(err => {
       toastShort(err);
-    }).finally(() => {
-      Dialog.dismissLoading();
-    })
+    });
   }
 
   setPageTitle() {
     if (this.state.messageInfo.articleTitle) {
       this.props.navigation.setOptions({
         headerTitle: () => (<HeaderTitle title={this.state.messageInfo.articleTitle}/>)
-      })
+      });
+      setTimeout(() => {
+        this.setState({
+          loading: false
+        });
+      }, 300);
     }
   }
 
@@ -63,86 +67,109 @@ export default class ViewArticle extends Component {
     Linking.openURL(utils.getImageUrl(url))
   }
 
-
   render() {
-    return (
-      <ScrollView
-        style={styles.container}
-        contentContainerStyle={$padding(0,0,32)}
-        stickyHeaderIndices={[utils.isNotEmpty(this.state.messageInfo.articleImages) ? 1 : 0]}>
-        { utils.isNotEmpty(this.state.messageInfo.articleImages) &&
-          <Swiper
-            style={{height: $width}}
-            autoplay={true}
-            autoplayTimeout={5}
-            renderPagination={(index,total) => <PagerView index={index+1} total={total}/> }
-            removeClippedSubviews={false}>
-            { this.state.messageInfo.articleImages.map((item, index) => {
-              return (
-                <Image
-                  key={index}
-                  style={{width: $width, height: $width}}
-                  source={{uri: utils.getImageUrl(item.articleImagePath)}}/>
-              );
-            })}
-          </Swiper>
-        }
-        <View style={styles.header}>
-          <TextView
-            style={styles.textTitle}>
-            {this.state.messageInfo.articleTitle}
-          </TextView>
-          <View style={ui.flexc}>
-            <MaterialCommunityIcons
-              name="clock-time-four-outline"
-              color={textSecondary}
-              size={12}/>
-            <TextView
-              style={styles.textDate}
-              numberOfLines={1}>
-              {this.state.messageInfo.createTime}
-            </TextView>
-          </View>
-          <View style={ui.flexc}>
-            <MaterialCommunityIcons
-              name="eye-check-outline"
-              size={12}
-              color={textPrimary}/>
+    if (this.state.loading) {
+      return (
+        <View style={styles.container}>
+          <VbeSkeleton
+            layout={[
+              {width: $width, height: $width}
+            ]}
+            animationDirection={"horizontalRight"}/>
+          <VbeSkeleton
+            style={styles.loadingView}
+            layout={[
+              {width: '90%', height: 20, marginTop: 8},
+              {width: '50%', height: 12, marginTop: 8},
+              {width: '100%', height: 15, marginTop: 24},
+              {width: '100%', height: 15, marginTop: 8},
+              {width: '100%', height: 15, marginTop: 8},
+              {width: '100%', height: 15, marginTop: 8},
+              {width: '30%', height: 15, marginTop: 8}
+            ]}
+            animationDirection={"horizontalRight"}/>
+        </View>
+      )
+    } else {
+      return (
+        <ScrollView
+          style={styles.container}
+          contentContainerStyle={$padding(0,0,32)}
+          stickyHeaderIndices={[utils.isNotEmpty(this.state.messageInfo.articleImages) ? 1 : 0]}>
+          { utils.isNotEmpty(this.state.messageInfo.articleImages) &&
+            <Swiper
+              style={{height: $width}}
+              autoplay={true}
+              autoplayTimeout={5}
+              renderPagination={(index,total) => <PagerView index={index+1} total={total}/> }
+              removeClippedSubviews={false}>
+              { this.state.messageInfo.articleImages.map((item, index) => {
+                return (
+                  <Image
+                    key={index}
+                    style={{width: $width, height: $width}}
+                    source={{uri: utils.getImageUrl(item.articleImagePath)}}/>
+                );
+              })}
+            </Swiper>
+          }
+          <View style={styles.header}>
             <TextView
-              style={styles.textView}
-              numberOfLines={1}>
-              {this.state.messageInfo.articleViews}
+              style={styles.textTitle}>
+              {this.state.messageInfo.articleTitle}
             </TextView>
+            <View style={ui.flexc}>
+              <MaterialCommunityIcons
+                name="clock-time-four-outline"
+                color={textSecondary}
+                size={12}/>
+              <TextView
+                style={styles.textDate}
+                numberOfLines={1}>
+                {this.state.messageInfo.createTime}
+              </TextView>
+            </View>
+            <View style={ui.flexc}>
+              <MaterialCommunityIcons
+                name="eye-check-outline"
+                size={12}
+                color={textPrimary}/>
+              <TextView
+                style={styles.textView}
+                numberOfLines={1}>
+                {this.state.messageInfo.articleViews}
+              </TextView>
+            </View>
+            <View style={ui.flex}>
+              <TextView 
+                style={styles.labelTypeText}
+                numberOfLines={1}>
+                {this.state.messageInfo.articleTypeName}
+              </TextView>
+            </View>
           </View>
-          <View style={ui.flex}>
-            <TextView 
-              style={styles.labelTypeText}
-              numberOfLines={1}>
-              {this.state.messageInfo.articleTypeName}
-            </TextView>
-          </View>
-        </View>
-        <TextView
-          style={styles.textMessage}
-          selectable={true}>
-          {this.state.messageInfo.articleContent}
-        </TextView>
-        { utils.isNotEmpty(this.state.messageInfo.articleLinks) &&
-          <>
-            <TextView style={styles.textLinkTitle}>{$t("notification.labelLinks")}</TextView>
-            { this.state.messageInfo.articleLinks.map((item, index) =>
-              <View style={styles.itemLink} key={index}>
-                <TextView style={styles.linkIndex}>{index + 1}.</TextView>
-                <TextView 
-                  style={styles.linkHyper}
-                  onPress={() => this.accessLink(item.articleLink)}>{item.articleLinkName}</TextView>
-              </View>
-            )}
-          </>
-        }
-        <EndView/>
-      </ScrollView>
-    );
+          <TextView
+            style={styles.textMessage}
+            selectable={true}>
+            {this.state.messageInfo.articleContent}
+          </TextView>
+          { utils.isNotEmpty(this.state.messageInfo.articleLinks) &&
+            <>
+              <TextView style={styles.textLinkTitle}>{$t("notification.labelLinks")}</TextView>
+              { this.state.messageInfo.articleLinks.map((item, index) =>
+                <View style={styles.itemLink} key={index}>
+                  <TextView style={styles.linkIndex}>{index + 1}.</TextView>
+                  <TextView 
+                    style={styles.linkHyper}
+                    onPress={() => this.accessLink(item.articleLink)}>{item.articleLinkName}</TextView>
+                </View>
+              )}
+            </>
+          }
+          <EndView/>
+        </ScrollView>
+      );
+    }
   }
 }
 
@@ -151,6 +178,12 @@ const styles = StyleSheet.create({
     flex: 1,
     backgroundColor: pageBackground
   },
+  loadingView: {
+    flex: 1,
+    padding: 16,
+    justifyContent: 'flex-start',
+    backgroundColor: pageBackground
+  },
   textTitle: {
     color: textPrimary,
     fontSize: 14,

+ 2 - 1
Strides-APP/app/pages/chargeV2/TabCharge.js

@@ -154,7 +154,8 @@ export default class TabCharge extends Component {
       name: this.state.stationInfo.name,
       address: this.state.stationInfo.address,
       chargeBoxId: this.state.connectorInfo.chargeBoxId,
-      connectorId: this.state.connectorInfo.connectorId
+      connectorId: this.state.connectorInfo.connectorId,
+      payPerUseAmount: this.state.stationInfo.payPerUseAmount
     });
   }
 

+ 50 - 7
Strides-APP/app/pages/chargingV2/ChargingPage.js

@@ -16,6 +16,8 @@ import StepCharging from './StepCharging';
 import StepStart from './StepStart';
 import StepStop from './StepStop';
 import PagerUtil from '../chargeV2/PagerUtil';
+import DialogPayPerUse from './DialogPayPerUse';
+import utils from '../../utils/utils';
 
 export default class ChargingPage extends Component {
   constructor(props) {
@@ -34,7 +36,8 @@ export default class ChargingPage extends Component {
       currentPerUse: "",
       currentPayment: PaymentDefault.DEFAULT.payType,
       currentPaytype: PaymentDefault.DEFAULT.payName,
-      selectedVoucher: {}
+      selectedVoucher: {},
+      showDialogPayPerUse: false
     };
     this.isPageShow = true;
     this.waitStartCharging = false;
@@ -264,18 +267,30 @@ export default class ChargingPage extends Component {
   }
 
   onStartCharge() {
-    this.setState({
-      isCharging: true
-    });
-    this.waitStartCharging = true;
     if (app.charge.paymentMethod) { //V3版本开始充电
-      this.onStartChargeV3();
+      if (utils.isNotEmpty(this.state.stationInfo.payPerUseAmount) && this.state.currentPayment?.code.indexOf(PAYTYPE.PAY_PER_USE_CONTAIN) >= 0) {
+        this.setState({
+          showDialogPayPerUse: true
+        });
+      } else {
+        this.onStartChargeV3();
+      }
       return;
     }
     if (this.state.currentPayment == PAYTYPE.PAY_PER_USE) { //V2版本PayPerUse
-      this.onStartChargePerUse();
+      if (utils.isNotEmpty(this.state.stationInfo.payPerUseAmount)) {
+        this.setState({
+          showDialogPayPerUse: true
+        });
+      } else {
+        this.onStartChargePerUse();
+      }
       return;
     }
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
     apiCharge.startCharge(this.state.stationInfo).then(res => {
       console.log("[开始充电-onStartCharge]", res);
       setTimeout(() => {
@@ -303,7 +318,27 @@ export default class ChargingPage extends Component {
     });
   }
 
+  backDialogPayPerUse(confirm) {
+    this.setState({
+      showDialogPayPerUse: false
+    });
+    if (confirm) {
+      if (app.charge.paymentMethod) { //V3版本开始充电
+        this.onStartChargeV3();
+        return;
+      }
+      if (this.state.currentPayment == PAYTYPE.PAY_PER_USE) { //V2版本PayPerUse
+        this.onStartChargePerUse();
+        return;
+      }
+    }
+  }
+
   onStartChargePerUse() {
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
     const params = {
       paymentOption: this.state.currentPayment,
       ...this.state.stationInfo
@@ -326,6 +361,10 @@ export default class ChargingPage extends Component {
   }
 
   onStartChargeV3() {
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
     const params = {
       sitePk: this.state.stationInfo.id,
       chargeBoxId: this.state.stationInfo.chargeBoxId,
@@ -490,6 +529,10 @@ export default class ChargingPage extends Component {
           onClose={() => {
             this.closeError();
           }}/>
+        <DialogPayPerUse
+          visible={this.state.showDialogPayPerUse}
+          amount={this.state.stationInfo.payPerUseAmount}
+          onClose={confirm => this.backDialogPayPerUse(confirm)}/>
       </View>
     );
   }

+ 81 - 0
Strides-APP/app/pages/chargingV2/DialogPayPerUse.js

@@ -0,0 +1,81 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { Modal } from 'react-native';
+import { Text, View } from 'react-native';
+import Button from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import TextView from '../../components/TextView';
+
+const DialogPayPerUse = ({
+  amount="",
+  visible,
+  onClose
+}) => (
+  <Modal
+    visible={visible}
+    transparent={true}>
+    <View style={styles.container}>
+      <View style={styles.content}>
+        <TextView style={styles.title}>{$t("payment.titleNote")}</TextView>
+        <TextView style={styles.message}>{$t("payment.tipsPayPerUseAmount").replace("{mm}", amount)}</TextView>
+        <View style={ui.flexc}>
+          <Button
+            style={styles.cancelButton}
+            text={$t("nav.cancel")}
+            textColor={textPrimary}
+            onClick={() => onClose(false)}/>
+          <Button
+            style={styles.confirmButton}
+            text={$t("nav.confirm")}
+            borderRadius={0}
+            onClick={() => onClose(true)}/>
+        </View>
+      </View>
+    </View>
+  </Modal>
+);
+
+export default DialogPayPerUse;
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: 'rgba(0,0,0,.1)'
+  },
+  content: {
+    width: Dialog.dialogWidth,
+    borderRadius: 4,
+    overflow: 'hidden',
+    backgroundColor: colorLight
+  },
+  title: {
+    paddingTop: 16,
+    paddingLeft: 16,
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  message: {
+    padding: 16,
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 16,
+    paddingBottom: 32
+  },
+  cancelButton: {
+    flex: 1,
+    borderColor: '#EEE',
+    borderWidth: 1,
+    borderRadius: 0,
+    backgroundColor: textButton
+  },
+  confirmButton: {
+    flex: 1,
+    borderColor: colorAccent,
+    borderWidth: 1,
+    borderRadius: 0,
+    backgroundColor: colorAccent
+  }
+})

+ 50 - 7
Strides-APP/app/pages/chargingV3/ChargingPage.js

@@ -16,6 +16,8 @@ import StepCharging from './StepCharging';
 import StepStart from './StepStart';
 import StepStop from './StepStop';
 import PagerUtil from '../chargeV2/PagerUtil';
+import DialogPayPerUse from './DialogPayPerUse';
+import utils from '../../utils/utils';
 
 export default class ChargingPageV4 extends Component {
   constructor(props) {
@@ -35,7 +37,8 @@ export default class ChargingPageV4 extends Component {
       currentPerUse: "",
       currentPayment: PaymentDefault.DEFAULT.payType,
       currentPaytype: PaymentDefault.DEFAULT.payName,
-      selectedVoucher: {}
+      selectedVoucher: {},
+      showDialogPayPerUse: false
     };
     this.isPageShow = true;
     this.waitStartCharging = false;
@@ -265,18 +268,30 @@ export default class ChargingPageV4 extends Component {
   }
 
   onStartCharge() {
-    this.setState({
-      isCharging: true
-    });
-    this.waitStartCharging = true;
     if (app.charge.paymentMethod) { //V3版本开始充电
-      this.onStartChargeV3();
+      if (utils.isNotEmpty(this.state.stationInfo.payPerUseAmount) && this.state.currentPayment?.code.indexOf(PAYTYPE.PAY_PER_USE_CONTAIN) >= 0) {
+        this.setState({
+          showDialogPayPerUse: true
+        });
+      } else {
+        this.onStartChargeV3();
+      }
       return;
     }
     if (this.state.currentPayment == PAYTYPE.PAY_PER_USE) { //V2版本PayPerUse
-      this.onStartChargePerUse();
+      if (utils.isNotEmpty(this.state.stationInfo.payPerUseAmount)) {
+        this.setState({
+          showDialogPayPerUse: true
+        });
+      } else {
+        this.onStartChargePerUse();
+      }
       return;
     }
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
     apiCharge.startCharge(this.state.stationInfo).then(res => {
       console.log("[开始充电-onStartCharge]", res);
       setTimeout(() => {
@@ -304,7 +319,27 @@ export default class ChargingPageV4 extends Component {
     });
   }
 
+  backDialogPayPerUse(confirm) {
+    this.setState({
+      showDialogPayPerUse: false
+    });
+    if (confirm) {
+      if (app.charge.paymentMethod) { //V3版本开始充电
+        this.onStartChargeV3();
+        return;
+      }
+      if (this.state.currentPayment == PAYTYPE.PAY_PER_USE) { //V2版本PayPerUse
+        this.onStartChargePerUse();
+        return;
+      }
+    }
+  }
+
   onStartChargePerUse() {
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
     const params = {
       paymentOption: this.state.currentPayment,
       ...this.state.stationInfo
@@ -327,6 +362,10 @@ export default class ChargingPageV4 extends Component {
   }
 
   onStartChargeV3() {
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
     const params = {
       sitePk: this.state.stationInfo.id,
       chargeBoxId: this.state.stationInfo.chargeBoxId,
@@ -492,6 +531,10 @@ export default class ChargingPageV4 extends Component {
           onClose={() => {
             this.closeError();
           }}/>
+        <DialogPayPerUse
+          visible={this.state.showDialogPayPerUse}
+          amount={this.state.stationInfo.payPerUseAmount}
+          onClose={confirm => this.backDialogPayPerUse(confirm)}/>
       </View>
     );
   }

+ 66 - 0
Strides-APP/app/pages/chargingV3/DialogPayPerUse.js

@@ -0,0 +1,66 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { Modal } from 'react-native';
+import { Text, View } from 'react-native';
+import Button from '../../components/Button';
+import Dialog from '../../components/Dialog';
+import TextView from '../../components/TextView';
+
+const DialogPayPerUse = ({
+  amount="",
+  visible,
+  onClose
+}) => (
+  <Modal
+    visible={visible}
+    transparent={true}>
+    <View style={styles.container}>
+      <View style={styles.content}>
+        <TextView style={styles.title}>{$t("payment.titleNote")}</TextView>
+        <TextView style={styles.message}>{$t("payment.tipsPayPerUseAmount").replace("{mm}", amount)}</TextView>
+        <Button
+          text={$t("nav.confirm")}
+          onClick={() => onClose(true)}/>
+        <Button
+          style={styles.cancelButton}
+          text={$t("nav.cancel")}
+          textColor={textPrimary}
+          onClick={() => onClose(false)}/>
+      </View>
+    </View>
+  </Modal>
+);
+
+export default DialogPayPerUse;
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: 'rgba(0,0,0,.1)'
+  },
+  content: {
+    width: Dialog.dialogWidth,
+    padding: 16,
+    borderRadius: 4,
+    backgroundColor: colorLight
+  },
+  title: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  message: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 16,
+    paddingBottom: 32
+  },
+  cancelButton: {
+    marginTop: 8,
+    borderColor: '#EEE',
+    borderWidth: 1,
+    backgroundColor: textButton
+  }
+})

+ 72 - 42
Strides-APP/app/pages/home/Home.js

@@ -15,6 +15,9 @@ import MyStatusBar from '../../components/MyStatusBar';
 import LocationPermission from './maps/LocationPermission';
 import MapUI from './MapUI';
 import MapUILumi from './MapUILumi';
+import storage from '../../utils/storage';
+
+const KEY_STORE_LIST = "map_stop_list"
 
 export default class HomePage extends Component {
   constructor(props) {
@@ -27,6 +30,8 @@ export default class HomePage extends Component {
         longitudeDelta: 0.0421
       },
       mapReady: false,
+      showStation: false,
+      stationId: "",
       stationInfo: {},
       stopList: [],
       hasPermission: isIOS,
@@ -42,6 +47,7 @@ export default class HomePage extends Component {
       parkingFee: 'ALL',
       connectorType: ''
     };
+    this.refresh = false;
     this.stateListener;
     this.forceUpdateDialog = undefined;
     this.locationListener = undefined;
@@ -60,15 +66,12 @@ export default class HomePage extends Component {
       }
     }
     navigation.addListener('focus', () => {
-      //toastShort('onResume')
-      this.setState({
-        stationInfo: {}
-      });
-      this.viewIndex = -1;
+      console.log('------Home------', "Focus")
+      this.onCloseInfo();
       SettingUtil.getSettings(set => {
         //console.log("获取设置信息", set);
         this.settingInfo = set;
-        this.checkPermission2Geo();
+        this.init();
       })
       this.isHide = false;
       if (!app.isWhitelabel) {
@@ -86,21 +89,6 @@ export default class HomePage extends Component {
         navigation.closeDrawer();
       }, 200);
     });
-    
-    /*navigation.addListener('beforeRemove', (e) => {
-      if (isIOS) {
-        e.preventDefault();
-      } else {
-        let time = new Date().getTime();
-        if (time - this.backSeconds < 2000) {
-          BackHandler.exitApp();
-        } else {
-          toastShort("Press the back button again to exit the program");
-          this.backSeconds = time;
-          e.preventDefault();
-        }
-      }
-    });*/
     this.stateListener = AppState.addEventListener("change", state => {
       if (state == 'active' && this.forceUpdateDialog) {
         this.showUpdateDialog(this.forceUpdateDialog);
@@ -120,7 +108,28 @@ export default class HomePage extends Component {
     if (this.stateListener) {
       this.stateListener.remove();
     }
-    BackHandler.removeEventListener("hardwareBackPress", this.toExit)
+    BackHandler.removeEventListener("hardwareBackPress", this.toExit);
+  }
+
+  init() {
+    if (this.state.stopList.length == 0 && !this.refresh) {
+      storage.getStorage(KEY_STORE_LIST).then(data => {
+        console.log("获取站点缓存", "成功");
+        if (data) {
+          const list = JSON.parse(data);
+          this.setState({
+            stopList: list
+          }, () => {
+            this.checkPermission2Geo(true);
+          })
+        } else {
+          this.checkPermission2Geo(true);
+        }
+      }).catch(err => {
+        console.log("获取站点缓存-err", err);
+        this.checkPermission2Geo(true);
+      })
+    }
   }
 
   toExit = () => {
@@ -159,7 +168,7 @@ export default class HomePage extends Component {
     this.setState({
       mapReady: true
     }, () => {
-      this.checkPermission2Geo();
+      this.init();
     });
   }
 
@@ -225,12 +234,16 @@ export default class HomePage extends Component {
   }
 
   checkPermission2Geo(refresh) {
+    if (this.refresh == refresh) {
+      return;
+    }
+    if (refresh) {
+      //避免关闭自动移动地图后无法点击按钮移动地图
+      this.refresh = true
+    }
+    console.log("checkPermission2Geo", refresh);
     this.checkGPS();
     if (this.state.hasPermission) {
-      if (refresh) {
-        //避免关闭自动移动地图后无法点击按钮移动地图
-        this.state.stopList = []
-      }
       this.infoGeoLocation();
     } else {
       LocationPermission.checkPermission((hasPermission, canRequestPermission) => {
@@ -285,8 +298,8 @@ export default class HomePage extends Component {
         let time = new Date().getTime();
         let interval = this.settingInfo.refreshInterval * 1000;//重复加载时间
         let first = this.state.stopList.length == 0;//是否第一次加载
-        if (first || time - this.refreshTime > interval) { //一分钟(10秒)加载一次
-          this.getStationList(region, first);
+        if (first || this.refresh || time - this.refreshTime > interval) { //一分钟(10秒)加载一次
+          this.getStationList(region, first || this.refresh);
           this.refreshTime = time;
         } else if (this.settingInfo.moveMyLocation) {
           this.setState({
@@ -316,10 +329,15 @@ export default class HomePage extends Component {
    * @param {boolean} first 是否初始化
    */
   getStationList(region, first) {
-    Dialog.showProgressDialog();
+    //Dialog.showProgressDialog();
     if (getUserId()) {
       this.filter.operaUserId = getUserId()
     }
+    if (region && (this.settingInfo.moveMyLocation || first)) {
+      this.setState({
+        region: region
+      });
+    }
     apiStation.getAllStation(this.filter).then(res => {
       Dialog.dismissLoading();
       if (res.data.sites) {
@@ -349,21 +367,18 @@ export default class HomePage extends Component {
         this.setState({
           stopList: list
         });
-        //this.viewChargeStation(list[0].id) //测试显示站点
-        if (region && (this.settingInfo.moveMyLocation || first)) {
-          setTimeout(() => {
-            this.setState({
-              region: region
-            });
-          }, 500);
+        if (this.filter.connectorType == "" && this.filter.parkingFee == "ALL") {
+          storage.setStorageJson(KEY_STORE_LIST, list);
         }
       }
+      this.refresh = false;
     }).catch(err => {
       Dialog.dismissLoading();
       toastShort(err);
-      this.setState({
+      /*this.setState({
         stopList: []
-      });
+      });*/
+      this.refresh = false;
     })
   }
 
@@ -371,6 +386,14 @@ export default class HomePage extends Component {
   viewChargeStation(id) {
     //console.log('info', this.state.stopList[index]);
     //const stationInfo = this.state.stopList[index];
+    if (this.state.stationId == id) {
+      return;
+    }
+    this.setState({
+      stationId: id,
+      stationInfo: {},
+      showStation: true
+    });
     if (app.modules.bookmarks) {
       for (let i = 0; i < this.state.stopList.length; i++) {
         let info = this.state.stopList[i];
@@ -388,7 +411,9 @@ export default class HomePage extends Component {
           latitudeDelta: 0.0922,
           longitudeDelta: 0.0421
         }
-        this.getStationInfo(id, region);
+        setTimeout(() => {
+          this.getStationInfo(id, region);
+        }, 500);
         /*if (this.settingInfo.alwaysLocation) {
           this.setState({
             region: region
@@ -397,7 +422,9 @@ export default class HomePage extends Component {
       });
     } else {
       //无定位权限仍然显示
-      this.getStationInfo(id, this.state.region);
+      setTimeout(() => {
+        this.getStationInfo(id, this.state.region);
+      }, 500);
     }
     //startPage(PageList.chargeDetail, {...this.state.stopList[index]});
   }
@@ -427,8 +454,11 @@ export default class HomePage extends Component {
   }
 
   onCloseInfo() {
+    this.viewIndex = -1;
     this.setState({
-      stationInfo: {}
+      stationId: "",
+      stationInfo: {},
+      showStation: false
     });
   }
 

+ 1 - 0
Strides-APP/app/pages/home/MapUI.js

@@ -73,6 +73,7 @@ export default MapUI = ({
           showUserLocation={showUserLocation}
         />
         <BottomSiteInfo
+          visible={state.showStation}
           stationInfo={state.stationInfo}
           onFavorite={onFavorite}
           onClose={onCloseInfo}

+ 1 - 0
Strides-APP/app/pages/home/MapUILumi.js

@@ -52,6 +52,7 @@ export default MapUILumi = ({
         onLocation={onLocation}
       /> */}
       <BottomSiteCard
+        visible={state.showStation}
         stationInfo={state.stationInfo}
         onFavorite={onFavorite}
         onClose={onCloseInfo}

+ 166 - 119
Strides-APP/app/pages/home/maps/BottomSiteCard.js

@@ -13,22 +13,26 @@ import app from '../../../../app.json';
 import TextView from '../../../components/TextView';
 import BottomModal from '../../../components/BottomModal';
 import SiteLabelView from '../../../components/SiteLabelView';
+import VbeSkeleton from '../../../components/VbeSkeleton';
 
 export const BottomSiteCard = ({
+  visible=false,
   stationInfo = {},
   onFavorite,
   onClose
 }) => {
-  const [visible, showDialog] = useState(false);
+  const [loading, showDialog] = useState(true);
 
   useEffect(() => {
     if (stationInfo.id) {
-      showDialog(true)
-    } else {
       showDialog(false)
     }
   }, [stationInfo]);
 
+  useEffect(() => {
+    showDialog(true);
+  }, [visible]);
+
   const getAvailable = (type) => {
     const all = stationInfo.allConnector;
     if (all) {
@@ -63,6 +67,7 @@ export const BottomSiteCard = ({
   }
 
   const toChargePage = () => {
+    onClose();
     if (stationInfo.upcoming) {
       toastShort($t("home.upcoming"))
     } else {
@@ -79,133 +84,175 @@ export const BottomSiteCard = ({
       }}
       backdropOpacity={0}
       contentStyle={styles.bottomModal}>
-      <View style={styles.stationBarView}>
-        <View style={ui.flexc}>
-          <TextView
-            ellipsizeMode='tail'
-            numberOfLines={1}
-            style={styles.stationTitle}>{stationInfo.name}</TextView>
-          <Pressable
-            style={styles.closeIcon}
-            android_ripple={rippleLess}
-            onPress={onClose}>
-            <MaterialCommunityIcons
-              name="close"
-              size={22}
-              color={textSecondary}/>
-          </Pressable>
-        </View>
-        <TextView
-          style={styles.stationAddress}
-          ellipsizeMode='tail'
-          numberOfLines={3}>{stationInfo.address}</TextView>
-        <View style={ui.flexc}>
-          <TextView style={styles.siteTypes}>{stationInfo.siteType}</TextView>
-          { (stationInfo.allConnector && stationInfo.allConnector.available > 0) &&
-            <TextView style={styles.stationAvailable}>
-              <MaterialCommunityIcons
-                name="circle"
-                size={10}
-                color={colorAccent}/>
-              <Text>  </Text>
-              {$t("charging.statusAvailable")}
-            </TextView>
-          }
-          <ConnectTypeV2 {...stationInfo?.acConnector}/>
-          <ConnectTypeV2 {...stationInfo?.dcConnector}/>
-        </View>
-        <View style={styles.infoDetailsView}>
-          <View style={ui.flex1}>
-            <TextView style={styles.infoTitle}>{$t("charging.operatingHours")}</TextView>
-            <View style={styles.infoView}>
-              <TextView style={styles.infoText}>{getOperatingHours()}</TextView>
-            </View>
+      { loading
+      ? <View style={styles.stationBarView}>
+          <View style={ui.flex}>
+            <VbeSkeleton
+              style={ui.flex1}
+              layout={[
+                {width: '80%', height: 20},
+                {width: '100%', height: 15, marginTop: 16},
+                {width: '60%', height: 15, marginTop: 4}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
           </View>
-          <View style={{width: 4}}></View>
-          <View style={ui.flex1}>
-            <TextView style={styles.infoTitle}>{$t("charging.parkingFees")}</TextView>
-            <View style={styles.infoView}>
-              <TextView style={styles.infoText}>{getParkingFee()}</TextView>
-            </View>
+          <EndView/>
+          <View style={ui.flex}>
+            <VbeSkeleton
+              style={[ui.flexc, ui.flex1]}
+              viewStyle={ui.flex1}
+              layout={[
+                {width: '16%', height: 15, marginRight: 8, borderRadius: 30},
+                {width: '16%', height: 15, marginRight: 8, borderRadius: 30},
+                {width: '16%', height: 15, marginRight: 8, borderRadius: 30},
+                {width: '16%', height: 15, borderRadius: 30}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
           </View>
-          <View style={{width: 4}}></View>
-          <View style={ui.flex1}>
-            <TextView style={styles.infoTitle}>{$t("charging.additionalInfo")}</TextView>
-            <View style={styles.infoView}>
-              <TextView style={styles.infoText}>{stationInfo?.additionalNotes}</TextView>
-            </View>
+          <EndView/>
+          <View style={ui.flex}>
+            <VbeSkeleton
+              style={[ui.flexc, ui.flex1]}
+              viewStyle={ui.flex1}
+              layout={[
+                {width: '20%', height: 25, marginRight: 12, borderRadius: 30},
+                {width: '20%', height: 25, marginRight: 12, borderRadius: 30},
+                {width: '20%', height: 25, marginRight: 12, borderRadius: 30}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
           </View>
         </View>
-        <EndView half/>
-        <View style={ui.flexc}>
-          { stationInfo.upcoming
-          ? <View style={[ui.center, $margin(0, 8)]}>
-              <MaterialIcons
-                name="upcoming"
-                size={42}
-                color={colorAccent}
-                style={styles.upcomingIcon} />
-              <TextView style={styles.upcomingText}>{$t("home.upcoming")}</TextView>
-            </View>
-          : <> 
+      : <View style={styles.stationBarView}>
+          <View style={ui.flexc}>
+            <TextView
+              ellipsizeMode='tail'
+              numberOfLines={1}
+              style={styles.stationTitle}>{stationInfo.name}</TextView>
             <Pressable
-              style={styles.directIconView}
-              onPress={() => {
-                utils.directMaps(stationInfo.latitude, stationInfo.longitude, stationInfo.address);
-              }}>
+              style={styles.closeIcon}
+              android_ripple={rippleLess}
+              onPress={onClose}>
               <MaterialCommunityIcons
-                name="directions"
-                size={20}
-                color={textPrimary}/>
-              <TextView style={styles.directText}>Directions</TextView>
+                name="close"
+                size={22}
+                color={textSecondary}/>
             </Pressable>
-            { app.modules.bookmarks &&
-              <Pressable
-                style={[styles.directIconView, stationInfo.favorite ? styles.bookmarked : {}]}
-                onPress={onFavorite}>
+          </View>
+          <TextView
+            style={styles.stationAddress}
+            ellipsizeMode='tail'
+            numberOfLines={3}>{stationInfo.address}</TextView>
+          <View style={ui.flexc}>
+            <TextView style={styles.siteTypes}>{stationInfo.siteType}</TextView>
+            { (stationInfo.allConnector && stationInfo.allConnector.available > 0) &&
+              <TextView style={styles.stationAvailable}>
+                <MaterialCommunityIcons
+                  name="circle"
+                  size={10}
+                  color={colorAccent}/>
+                <Text>  </Text>
+                {$t("charging.statusAvailable")}
+              </TextView>
+            }
+            <ConnectTypeV2 {...stationInfo?.acConnector}/>
+            <ConnectTypeV2 {...stationInfo?.dcConnector}/>
+          </View>
+          <View style={styles.infoDetailsView}>
+            <View style={ui.flex1}>
+              <TextView style={styles.infoTitle}>{$t("charging.operatingHours")}</TextView>
+              <View style={styles.infoView}>
+                <TextView style={styles.infoText}>{getOperatingHours()}</TextView>
+              </View>
+            </View>
+            <View style={{width: 4}}></View>
+            <View style={ui.flex1}>
+              <TextView style={styles.infoTitle}>{$t("charging.parkingFees")}</TextView>
+              <View style={styles.infoView}>
+                <TextView style={styles.infoText}>{getParkingFee()}</TextView>
+              </View>
+            </View>
+            <View style={{width: 4}}></View>
+            <View style={ui.flex1}>
+              <TextView style={styles.infoTitle}>{$t("charging.additionalInfo")}</TextView>
+              <View style={styles.infoView}>
+                <TextView style={styles.infoText}>{stationInfo?.additionalNotes}</TextView>
+              </View>
+            </View>
+          </View>
+          <EndView half/>
+          <View style={ui.flexc}>
+            { stationInfo.upcoming
+            ? <View style={[ui.center, $margin(0, 8)]}>
                 <MaterialIcons
-                  name="star"
+                  name="upcoming"
+                  size={42}
+                  color={colorAccent}
+                  style={styles.upcomingIcon} />
+                <TextView style={styles.upcomingText}>{$t("home.upcoming")}</TextView>
+              </View>
+            : <> 
+              <Pressable
+                style={styles.directIconView}
+                onPress={() => {
+                  utils.directMaps(stationInfo.latitude, stationInfo.longitude, stationInfo.address);
+                }}>
+                <MaterialCommunityIcons
+                  name="directions"
                   size={20}
-                  color={stationInfo.favorite ? textLight : textPrimary}/>
-                <TextView style={[styles.directText, stationInfo.favorite ? styles.bookmarked : {}]}>
-                  {stationInfo.favorite ? "Saved" : "Save"}
-                </TextView>
+                  color={textPrimary}/>
+                <TextView style={styles.directText}>Directions</TextView>
               </Pressable>
-            }
-            {/* <Pressable
-              style={styles.directIconView}
-              onPress={() => {
-                startPage(PageList.scanqr, {actionDetail: true});
-              }}>
-              <MaterialCommunityIcons
-                name="qrcode-scan"
-                size={12}
-                color={textPrimary}
-                style={$padding(3)}/>
-              <TextView style={styles.directText}>Scan</TextView>
-            </Pressable> */}
-            <Pressable
-              style={styles.directIconView}
-              onPress={() => toChargePage()}>
-              <MaterialCommunityIcons
-                name="information-variant"
-                size={20}
-                color={textPrimary}/>
-              <TextView style={styles.directText}>More Info</TextView>
-            </Pressable>
-          </> }
-        </View>
-        {/* stationInfo?.labels?.length > 0 &&
-          <View style={styles.siteLabelsView}>
-            <Image
-              style={styles.labelIcon}
-              source={require('../../../images/maps/ic_marker_additional.png')}/>
-            { stationInfo.labels.map((item, index) => 
-              <SiteLabelView key={index} {...item}/>
-            )}
+              { app.modules.bookmarks &&
+                <Pressable
+                  style={[styles.directIconView, stationInfo.favorite ? styles.bookmarked : {}]}
+                  onPress={onFavorite}>
+                  <MaterialIcons
+                    name="star"
+                    size={20}
+                    color={stationInfo.favorite ? textLight : textPrimary}/>
+                  <TextView style={[styles.directText, stationInfo.favorite ? styles.bookmarked : {}]}>
+                    {stationInfo.favorite ? "Saved" : "Save"}
+                  </TextView>
+                </Pressable>
+              }
+              {/* <Pressable
+                style={styles.directIconView}
+                onPress={() => {
+                  startPage(PageList.scanqr, {actionDetail: true});
+                }}>
+                <MaterialCommunityIcons
+                  name="qrcode-scan"
+                  size={12}
+                  color={textPrimary}
+                  style={$padding(3)}/>
+                <TextView style={styles.directText}>Scan</TextView>
+              </Pressable> */}
+              <Pressable
+                style={styles.directIconView}
+                onPress={() => toChargePage()}>
+                <MaterialCommunityIcons
+                  name="information-variant"
+                  size={20}
+                  color={textPrimary}/>
+                <TextView style={styles.directText}>More Info</TextView>
+              </Pressable>
+            </> }
           </View>
-        */}
-      </View>
+          {/* stationInfo?.labels?.length > 0 &&
+            <View style={styles.siteLabelsView}>
+              <Image
+                style={styles.labelIcon}
+                source={require('../../../images/maps/ic_marker_additional.png')}/>
+              { stationInfo.labels.map((item, index) => 
+                <SiteLabelView key={index} {...item}/>
+              )}
+            </View>
+          */}
+        </View>
+      }
     </BottomModal>
   )
 }

+ 183 - 100
Strides-APP/app/pages/home/maps/BottomSiteInfo.js

@@ -2,7 +2,7 @@
  * 地图底部充电站信息组件
  * @邠心vbe on 2022/12/23
  */
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { Pressable, StyleSheet, View, Text, Image } from 'react-native';
 import { ElevationObject } from '../../../components/Button';
 import utils from '../../../utils/utils';
@@ -12,13 +12,29 @@ import ConnectType from '../../search/ConnectType';
 import app from '../../../../app.json';
 import TextView from '../../../components/TextView';
 import SiteLabelView from '../../../components/SiteLabelView';
+import VbeSkeleton from '../../../components/VbeSkeleton';
 
 export default BottomSiteInfo = ({
+  visible=false,
   stationInfo = {},
   onFavorite,
   onClose,
   style=styles.stationBarView
 }) => {
+  const [loading, showLoading] = useState(true);
+
+  useEffect(() => {
+    if (stationInfo.id) {
+      showLoading(false)
+    } else {
+      showLoading(true)
+    }
+  }, [stationInfo]);
+
+  useEffect(() => {
+    showLoading(true);
+  }, [visible]);
+
   const getAvailable = (type) => {
     const all = stationInfo.allConnector;
     if (all) {
@@ -61,118 +77,185 @@ export default BottomSiteInfo = ({
     }
   }
 
-  if (stationInfo.id) {
-    return (
-      <Pressable
-        style={({pressed}) => [style, pressed ? styles.stationBarPresed : {}]}
-        onPress={() => toChargePage()}>
-        <View style={ui.flexc}>
-          <TextView
-            ellipsizeMode='tail'
-            numberOfLines={1}
-            style={styles.stationTitle}>{stationInfo.name}</TextView>
-          <Pressable
-            style={styles.closeIcon}
-            android_ripple={rippleLess}
-            onPress={onClose}>
-            <MaterialCommunityIcons
-              name="close"
-              size={22}
-              color={textCancel}/>
-          </Pressable>
-        </View>
-        <View style={{height: 4}}></View>
-        <View style={ui.flexc}>
-          <TextView
-            style={styles.stationAddress}
-            ellipsizeMode='tail'
-            numberOfLines={3}>{stationInfo.address}</TextView>
-          { stationInfo.upcoming
-          ? <View style={[ui.center, $margin(0, 8)]}>
-              <MaterialIcons
-                name="upcoming"
-                size={42}
-                color={colorAccent}
-                style={styles.upcomingIcon} />
-              <TextView style={styles.upcomingText}>{$t("home.upcoming")}</TextView>
-            </View>
-          : <> 
-            { app.modules.bookmarks &&
-              <Pressable
-                style={[styles.directIconView, {backgroundColor: stationInfo.favorite ? colorPrimary : colorCancel}]}
-                android_ripple={rippleLess}
-                onPress={onFavorite}>
-                <MaterialIcons
-                  name="star"
-                  size={26}
-                  color={colorLight}/>
-              </Pressable>
-            }
+  if (visible) {
+    if (loading) {
+      return (
+        <View style={style}>
+          <View style={ui.flex}>
+            <VbeSkeleton
+              style={ui.flex1}
+              layout={[
+                {width: '60%', height: 20, marginTop: 4}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
             <Pressable
-              style={styles.directIconView}
+              style={styles.closeIcon}
               android_ripple={rippleLess}
-              onPress={() => {
-                utils.directMaps(stationInfo.latitude, stationInfo.longitude, stationInfo.address);
-              }}>
+              onPress={onClose}>
               <MaterialCommunityIcons
-                name="navigation-variant"
-                size={25}
-                color={colorLight}/>
-              {/* <MaterialIcons
-                name='directions'
-                size={28}
-                color={colorAccent}/>
-              <Text style={styles.distanceText}>{stationInfo.distance}</Text> */}
+                name="close"
+                size={22}
+                color={textCancel}/>
             </Pressable>
-          </> }
-        </View>
-        <View style={ui.flex}>
-          <View style={styles.connectView}>
-            <ConnectType {...stationInfo?.acConnector}/>
-            <ConnectType {...stationInfo?.dcConnector}/>
           </View>
-          <View style={styles.stationStatusView}>
-            { (stationInfo.allConnector && stationInfo.allConnector.available > 0) &&
-              <TextView style={[ChargeStyle.infoStatus, ChargeStyle.statusAvailable, styles.stationStatus]}>{$t("charging.statusAvailable")}</TextView>
-            }
-            <TextView style={[ChargeStyle.infoStatus, stationInfo.siteType == 'Public' ? ChargeStyle.statusAvailable : ChargeStyle.statusWarning, styles.siteTypes]}>{stationInfo.siteType}</TextView>
+          <EndView half/>
+          <View style={ui.flexc}>
+            <VbeSkeleton
+              style={ui.flex1}
+              layout={[
+                {width: '100%', height: 15, marginTop: 4},
+                {width: '60%', height: 15, marginTop: 4}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
+            <VbeSkeleton
+              style={ui.flexc}
+              layout={[
+                {width: 42, height: 42, marginLeft: 16, borderRadius: 48},
+                {width: 42, height: 42, marginLeft: 8, borderRadius: 48}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
+          </View>
+          <EndView/>
+          <View style={ui.flex}>
+            <VbeSkeleton
+              style={[ui.flexc, ui.flex1]}
+              viewStyle={ui.flex1}
+              layout={[
+                {width: '16%', height: 20, marginRight: 8, borderRadius: 30},
+                {width: '16%', height: 20, marginRight: 8, borderRadius: 30},
+                {width: '16%', height: 20, marginRight: 8, borderRadius: 30}
+              ]}
+              animationDirection={"horizontalRight"}
+            />
           </View>
+          <EndView/>
+          <VbeSkeleton
+            style={ui.flex1}
+            layout={[
+              {width: '100%', height: 12},
+              {width: '60%', height: 12, marginTop: 4}
+            ]}
+            animationDirection={"horizontalRight"}
+          />
+          <EndView/>
         </View>
-        { stationInfo?.labels?.length > 0 &&
-          <View style={styles.siteLabelsView}>
-            <Image
-              style={styles.labelIcon}
-              source={require('../../../images/maps/ic_marker_additional.png')}/>
-            { stationInfo.labels.map((item, index) => 
-              <SiteLabelView key={index} {...item}/>
-            )}
+      );
+    } else {
+      return (
+        <Pressable
+          style={({pressed}) => [style, pressed ? styles.stationBarPresed : {}]}
+          onPress={() => toChargePage()}>
+          <View style={ui.flexc}>
+            <TextView
+              ellipsizeMode='tail'
+              numberOfLines={1}
+              style={styles.stationTitle}>{stationInfo.name}</TextView>
+            <Pressable
+              style={styles.closeIcon}
+              android_ripple={rippleLess}
+              onPress={onClose}>
+              <MaterialCommunityIcons
+                name="close"
+                size={22}
+                color={textCancel}/>
+            </Pressable>
           </View>
-        }
-        <View style={styles.divideLine}></View>
-        <View style={styles.infoDetailsView}>
-          <View style={ui.flex1}>
-            <TextView style={styles.infoTitle}>{$t("charging.operatingHours")}</TextView>
-            <View style={styles.infoView}>
-              <TextView style={styles.infoText}>{getOperatingHours()}</TextView>
-            </View>
+          <View style={{height: 4}}></View>
+          <View style={ui.flexc}>
+            <TextView
+              style={styles.stationAddress}
+              ellipsizeMode='tail'
+              numberOfLines={3}>{stationInfo.address}</TextView>
+            { stationInfo.upcoming
+            ? <View style={[ui.center, $margin(0, 8)]}>
+                <MaterialIcons
+                  name="upcoming"
+                  size={42}
+                  color={colorAccent}
+                  style={styles.upcomingIcon} />
+                <TextView style={styles.upcomingText}>{$t("home.upcoming")}</TextView>
+              </View>
+            : <> 
+              { app.modules.bookmarks &&
+                <Pressable
+                  style={[styles.directIconView, {backgroundColor: stationInfo.favorite ? colorPrimary : colorCancel}]}
+                  android_ripple={rippleLess}
+                  onPress={onFavorite}>
+                  <MaterialIcons
+                    name="star"
+                    size={26}
+                    color={colorLight}/>
+                </Pressable>
+              }
+              <Pressable
+                style={styles.directIconView}
+                android_ripple={rippleLess}
+                onPress={() => {
+                  utils.directMaps(stationInfo.latitude, stationInfo.longitude, stationInfo.address);
+                }}>
+                <MaterialCommunityIcons
+                  name="navigation-variant"
+                  size={25}
+                  color={colorLight}/>
+                {/* <MaterialIcons
+                  name='directions'
+                  size={28}
+                  color={colorAccent}/>
+                <Text style={styles.distanceText}>{stationInfo.distance}</Text> */}
+              </Pressable>
+            </> }
           </View>
-          <View style={{width: 4}}></View>
-          <View style={ui.flex1}>
-            <TextView style={styles.infoTitle}>{$t("charging.parkingFees")}</TextView>
-            <View style={styles.infoView}>
-              <TextView style={styles.infoText}>{getParkingFee()}</TextView>
+          <View style={ui.flex}>
+            <View style={styles.connectView}>
+              <ConnectType {...stationInfo?.acConnector}/>
+              <ConnectType {...stationInfo?.dcConnector}/>
+            </View>
+            <View style={styles.stationStatusView}>
+              { (stationInfo.allConnector && stationInfo.allConnector.available > 0) &&
+                <TextView style={[ChargeStyle.infoStatus, ChargeStyle.statusAvailable, styles.stationStatus]}>{$t("charging.statusAvailable")}</TextView>
+              }
+              <TextView style={[ChargeStyle.infoStatus, stationInfo.siteType == 'Public' ? ChargeStyle.statusAvailable : ChargeStyle.statusWarning, styles.siteTypes]}>{stationInfo.siteType}</TextView>
             </View>
           </View>
-          <View style={{width: 4}}></View>
-          <View style={ui.flex1}>
-            <TextView style={styles.infoTitle}>{$t("charging.additionalInfo")}</TextView>
-            <View style={styles.infoView}>
-              <TextView style={styles.infoText}>{stationInfo?.additionalNotes}</TextView>
+          { stationInfo?.labels?.length > 0 &&
+            <View style={styles.siteLabelsView}>
+              <Image
+                style={styles.labelIcon}
+                source={require('../../../images/maps/ic_marker_additional.png')}/>
+              { stationInfo.labels.map((item, index) => 
+                <SiteLabelView key={index} {...item}/>
+              )}
+            </View>
+          }
+          <View style={styles.divideLine}></View>
+          <View style={styles.infoDetailsView}>
+            <View style={ui.flex1}>
+              <TextView style={styles.infoTitle}>{$t("charging.operatingHours")}</TextView>
+              <View style={styles.infoView}>
+                <TextView style={styles.infoText}>{getOperatingHours()}</TextView>
+              </View>
+            </View>
+            <View style={{width: 4}}></View>
+            <View style={ui.flex1}>
+              <TextView style={styles.infoTitle}>{$t("charging.parkingFees")}</TextView>
+              <View style={styles.infoView}>
+                <TextView style={styles.infoText}>{getParkingFee()}</TextView>
+              </View>
+            </View>
+            <View style={{width: 4}}></View>
+            <View style={ui.flex1}>
+              <TextView style={styles.infoTitle}>{$t("charging.additionalInfo")}</TextView>
+              <View style={styles.infoView}>
+                <TextView style={styles.infoText}>{stationInfo?.additionalNotes}</TextView>
+              </View>
             </View>
           </View>
-        </View>
-      </Pressable>
-    );
+        </Pressable>
+      );
+    }
   } else {
     return <></>;
   }

+ 2 - 1
Strides-APP/app/pages/payment/PaymentConfig.js

@@ -5,7 +5,8 @@ import { PageList } from '../Router';
 
 export const PAYTYPE = {
   CREDIT_WALLET: "CreditWallet", // 余额支付
-  PAY_PER_USE: "PayPerUse" // 按次支付
+  PAY_PER_USE: "PayPerUse", // 按次支付
+  PAY_PER_USE_CONTAIN: "pay_per_use"
 }
 
 /**

+ 62 - 25
Strides-APP/app/pages/vouchers/PointsHistory.js

@@ -8,7 +8,7 @@ import { MyRefreshProps } from '../../components/ThemesConfig';
 import TextView from '../../components/TextView';
 import Dialog from '../../components/Dialog';
 import apiVoucher from '../../api/apiVoucher';
-import app from '../../../app.json';
+import VbeSkeleton from '../../components/VbeSkeleton';
 
 export default class PointsHistory extends Component {
   constructor(props) {
@@ -16,12 +16,13 @@ export default class PointsHistory extends Component {
     this.state = {
       dataList: [],
       hasMore: true,
-      refreshing: false
+      loading: true,
+      refreshing: false,
+      loadingList: ["","","",""]
     };
   }
 
   componentDidMount() {
-    Dialog.showProgressDialog();
     this.getHistoryList();
   }
 
@@ -69,9 +70,9 @@ export default class PointsHistory extends Component {
       toastShort(err)
     }).finally(() => {
       this.setState({
+        loading: false,
         refreshing: false
       });
-      Dialog.dismissLoading();
     });
   }
 
@@ -110,26 +111,52 @@ export default class PointsHistory extends Component {
   }
 
   render() {
-    return (
-      <FlatList
-        style={styles.listView}
-        data={this.state.dataList}
-        renderItem={this.listItem}
-        ItemSeparatorComponent={this.divideView}
-        ListFooterComponent={this.bottomView}
-        keyExtractor={item => item.pointsHistoryId}
-        onEndReached={() => this.getNextPage()}
-        onEndReachedThreshold={0.3}
-        refreshControl={
-          <RefreshControl
-            {...MyRefreshProps()}
-            refreshing={this.state.refreshing}
-            onRefresh={() => this.onRefresh()}
-          />
-        }
-        ListEmptyComponent={<Text style={styles.noData}>{$t('points.noData')}</Text>}
-      />
-    );
+    if (this.state.loading) {
+      return (
+        <View style={styles.listView}>
+          { this.state.loadingList.map((item, index) =>
+            <View style={styles.loadingView} key={index}>
+              <VbeSkeleton
+                style={styles.iconDefault}
+                layout={[
+                  {width: 30, height: 30, borderRadius: 30}
+                ]}
+                animationDirection={"horizontalRight"}
+              />
+              <VbeSkeleton
+                style={ui.flex1}
+                layout={[
+                  {width: '60%', height: 18},
+                  {width: '80%', height: 12, marginTop: 8}
+                ]}
+                animationDirection={"horizontalRight"}
+              />
+            </View>
+          )}
+        </View>
+      )
+    } else {
+      return (
+        <FlatList
+          style={styles.listView}
+          data={this.state.dataList}
+          renderItem={this.listItem}
+          ItemSeparatorComponent={this.divideView}
+          ListFooterComponent={this.bottomView}
+          keyExtractor={item => item.pointsHistoryId}
+          onEndReached={() => this.getNextPage()}
+          onEndReachedThreshold={0.3}
+          refreshControl={
+            <RefreshControl
+              {...MyRefreshProps()}
+              refreshing={this.state.refreshing}
+              onRefresh={() => this.onRefresh()}
+            />
+          }
+          ListEmptyComponent={<Text style={styles.noData}>{$t('points.noData')}</Text>}
+        />
+      );
+    }
   }
 }
 
@@ -143,6 +170,11 @@ const styles = StyleSheet.create({
     alignItems: 'center',
     flexDirection: 'row'
   },
+  loadingView: {
+    padding: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
   itemContent: {
     flex: 1,
     marginLeft: 8,
@@ -165,6 +197,11 @@ const styles = StyleSheet.create({
     borderRadius: 30,
     borderColor: textPrimary
   },
+  iconDefault: {
+    width: 28,
+    height: 28,
+    marginRight: 16
+  },
   issueName: {
     color: textPrimary,
     fontSize: 12,
@@ -192,7 +229,7 @@ const styles = StyleSheet.create({
   },
   noMore: {
     color: textPlacehoder,
-    fontSize: 8,
+    fontSize: 10,
     padding: 16,
     textAlign: 'center'
   }

+ 41 - 1
Strides-APP/app/pages/vouchers/VoucherSelect.js

@@ -8,6 +8,7 @@ import BadgeSelectItem from '../../components/BadgeSelectItem';
 import Button from '../../components/Button';
 import PagerUtil from '../chargeV2/PagerUtil';
 import app from '../../../app.json';
+import VbeSkeleton from '../../components/VbeSkeleton';
 
 export default class VoucherSelect extends Component {
   constructor(props) {
@@ -16,8 +17,10 @@ export default class VoucherSelect extends Component {
       dataList: [],
       voucherType: "",
       hasMore: true,
+      loading: true,
       refreshing: false,
-      selectedVoucher: {}
+      selectedVoucher: {},
+      loadingList: ["","","",""]
     };
     this.params = {
       chargeBoxId: "",
@@ -88,6 +91,11 @@ export default class VoucherSelect extends Component {
         refreshing: false,
         selectedVoucher: {}
       });
+      setTimeout(() => {
+        this.setState({
+          loading: false
+        });
+      }, 500);
     });
   }
 
@@ -181,6 +189,32 @@ export default class VoucherSelect extends Component {
   }
 
   render() {
+    if (this.state.loading) {
+      return (
+        <View style={styles.container}>
+          { this.state.loadingList.map((item, index) =>
+            <View style={styles.loadingView} key={index}>
+              <VbeSkeleton
+                style={ui.flex1}
+                layout={[
+                  {width: '50%', height: 18},
+                  {width: '90%', height: 12, marginTop: 8},
+                  {width: '40%', height: 12, marginTop: 4}
+                ]}
+                animationDirection={"horizontalRight"}
+              />
+              <VbeSkeleton
+                style={{width: 56}}
+                layout={[
+                  {width: 56, height: 30, borderRadius: 30}
+                ]}
+                animationDirection={"horizontalRight"}
+              />
+            </View>
+          )}
+        </View>
+      )
+    }
     return (
       <View style={styles.container}>
         <FlatList
@@ -226,6 +260,12 @@ const styles = StyleSheet.create({
     backgroundColor: colorLight,
     ...$padding(0, 16)
   },
+  loadingView: {
+    paddingTop: 16,
+    paddingBottom: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
   itemBox: {
     top: 0,
     left: 0,

+ 58 - 26
Strides-APP/app/pages/wallet/HistoryList.js

@@ -9,6 +9,7 @@ import apiWallet from '../../api/apiWallet';
 import TextView from '../../components/TextView';
 import Dialog from '../../components/Dialog';
 import { PageList } from '../Router';
+import VbeSkeleton from '../../components/VbeSkeleton';
 
 const IconCharge = require('../../images/wallet/ic-type-charge.png');
 const IconPayment = require('../../images/wallet/ic-type-payment.png');
@@ -17,15 +18,18 @@ export default class HistoryList extends Component {
   constructor(props) {
     super(props);
     this.state = {
+      loading: true,
       dataList: [],
       hasMore: true,
-      refreshing: false
+      refreshing: false,
+      loadingList: ["", "", "", ""]
     };
   }
 
   componentDidMount() {
-    Dialog.showProgressDialog();
-    this.getHistoryList();
+    setTimeout(() => {
+      this.getHistoryList();
+    }, 500);
   }
 
   onRefresh() {
@@ -72,9 +76,9 @@ export default class HistoryList extends Component {
       toastShort(err)
     }).finally(() => {
       this.setState({
+        loading: false,
         refreshing: false
       });
-      Dialog.dismissLoading();
     });
   }
 
@@ -135,26 +139,54 @@ export default class HistoryList extends Component {
   }
 
   render() {
-    return (
-      <FlatList
-        style={styles.listView}
-        data={this.state.dataList}
-        renderItem={this.listItem}
-        ItemSeparatorComponent={this.divideView}
-        ListFooterComponent={this.bottomView}
-        keyExtractor={item => item.creditRecordPk}
-        onEndReached={() => this.getNextPage()}
-        onEndReachedThreshold={0.3}
-        refreshControl={
-          <RefreshControl
-            {...MyRefreshProps()}
-            refreshing={this.state.refreshing}
-            onRefresh={() => this.onRefresh()}
-          />
-        }
-        ListEmptyComponent={<Text style={styles.noData}>{$t('wallet.noHistoryData')}</Text>}
-      />
-    );
+    if (this.state.loading) {
+      return (
+        <View>
+          { this.state.loadingList.map((item, index) =>
+            <View style={styles.itemView} key={index}>
+              <VbeSkeleton
+                style={styles.iconType}
+                layout={[
+                  {width: 32, height: 32, borderRadius: 30, marginRight: 16}
+                ]}
+                animationDirection={"horizontalRight"}
+              />
+              <View style={styles.itemContent}>
+                <VbeSkeleton
+                  style={ui.flex1}
+                  layout={[
+                    {width: '100%', height: 15, marginTop: 4},
+                    {width: '60%', height: 15, marginTop: 4}
+                  ]}
+                  animationDirection={"horizontalRight"}
+                />
+              </View>
+            </View>
+          )}
+        </View>
+      )
+    } else {
+      return (
+        <FlatList
+          style={styles.listView}
+          data={this.state.dataList}
+          renderItem={this.listItem}
+          ItemSeparatorComponent={this.divideView}
+          ListFooterComponent={this.bottomView}
+          keyExtractor={item => item.creditRecordPk}
+          onEndReached={() => this.getNextPage()}
+          onEndReachedThreshold={0.3}
+          refreshControl={
+            <RefreshControl
+              {...MyRefreshProps()}
+              refreshing={this.state.refreshing}
+              onRefresh={() => this.onRefresh()}
+            />
+          }
+          ListEmptyComponent={<Text style={styles.noData}>{$t('wallet.noHistoryData')}</Text>}
+        />
+      );
+    }
   }
 }
 
@@ -182,8 +214,8 @@ const styles = StyleSheet.create({
     borderTopColor: '#eee',
   },
   iconType: {
-    width: 28,
-    height: 28
+    width: 32,
+    height: 32
   },
   issueName: {
     color: textPrimary,

+ 2 - 1
Strides-APP/app/utils/utils.js

@@ -80,7 +80,8 @@ export default {
         additionalNotes: obj.additionalNotes,
         endlessService: obj.endlessService,
         serviceProvider: obj.serviceProvider,
-        enableReservation: obj.enableReservation
+        enableReservation: obj.enableReservation,
+        payPerUseAmount: obj.payPerUseAmount
       }
     } else {
       return {id: 0}