Browse Source

add code files

vbea 3 years ago
parent
commit
2dd9b4bce0
100 changed files with 3788 additions and 0 deletions
  1. 6 0
      Strides-APP/.buckconfig
  2. 3 0
      Strides-APP/.editorconfig
  3. 4 0
      Strides-APP/.eslintrc.js
  4. 66 0
      Strides-APP/.flowconfig
  5. 3 0
      Strides-APP/.gitattributes
  6. 8 0
      Strides-APP/.prettierrc.js
  7. 1 0
      Strides-APP/.watchmanconfig
  8. 146 0
      Strides-APP/App.js
  9. 149 0
      Strides-APP/README.md
  10. 55 0
      Strides-APP/android/app/BUCK
  11. 274 0
      Strides-APP/android/app/build.gradle
  12. 19 0
      Strides-APP/android/app/build_defs.bzl
  13. 39 0
      Strides-APP/android/app/google-services.json
  14. 10 0
      Strides-APP/android/app/proguard-rules.pro
  15. 13 0
      Strides-APP/android/app/src/debug/AndroidManifest.xml
  16. 72 0
      Strides-APP/android/app/src/debug/java/com/strides/chargeco/ReactNativeFlipper.java
  17. 109 0
      Strides-APP/android/app/src/main/AndroidManifest.xml
  18. 22 0
      Strides-APP/android/app/src/main/java/com/strides/chargeco/MainActivity.java
  19. 90 0
      Strides-APP/android/app/src/main/java/com/strides/chargeco/MainApplication.java
  20. BIN
      Strides-APP/android/app/src/main/res/drawable/ic_notification.png
  21. 24 0
      Strides-APP/android/app/src/main/res/layout/marker_cluster.xml
  22. 24 0
      Strides-APP/android/app/src/main/res/layout/marker_cluster_unavailable.xml
  23. 7 0
      Strides-APP/android/app/src/main/res/layout/marker_site.xml
  24. BIN
      Strides-APP/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  25. BIN
      Strides-APP/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  26. BIN
      Strides-APP/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  27. BIN
      Strides-APP/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  28. BIN
      Strides-APP/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  29. BIN
      Strides-APP/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  30. BIN
      Strides-APP/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  31. BIN
      Strides-APP/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  32. BIN
      Strides-APP/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  33. BIN
      Strides-APP/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  34. 6 0
      Strides-APP/android/app/src/main/res/values/colors.xml
  35. 5 0
      Strides-APP/android/app/src/main/res/values/google_maps_api.xml
  36. 4 0
      Strides-APP/android/app/src/main/res/values/strings.xml
  37. 12 0
      Strides-APP/android/app/src/main/res/values/styles.xml
  38. 5 0
      Strides-APP/android/app/src/main/res/xml/filepaths.xml
  39. 2 0
      Strides-APP/android/app/version.properties
  40. 44 0
      Strides-APP/android/build.gradle
  41. 29 0
      Strides-APP/android/gradle.properties
  42. BIN
      Strides-APP/android/gradle/wrapper/gradle-wrapper.jar
  43. 5 0
      Strides-APP/android/gradle/wrapper/gradle-wrapper.properties
  44. 185 0
      Strides-APP/android/gradlew
  45. 89 0
      Strides-APP/android/gradlew.bat
  46. 6 0
      Strides-APP/android/settings.gradle
  47. 8 0
      Strides-APP/app.json
  48. 46 0
      Strides-APP/app/api/apiCharge.js
  49. 18 0
      Strides-APP/app/api/apiStation.js
  50. 36 0
      Strides-APP/app/api/apiUpload.js
  51. 66 0
      Strides-APP/app/api/apiUser.js
  52. 30 0
      Strides-APP/app/api/apiWallet.js
  53. 188 0
      Strides-APP/app/api/http.js
  54. 78 0
      Strides-APP/app/components/Appbar.js
  55. 39 0
      Strides-APP/app/components/BottomModal.js
  56. 209 0
      Strides-APP/app/components/Button.js
  57. 41 0
      Strides-APP/app/components/CheckBox.js
  58. 45 0
      Strides-APP/app/components/CheckBoxText.js
  59. 496 0
      Strides-APP/app/components/CountryIcon.js
  60. 446 0
      Strides-APP/app/components/Dialog.js
  61. 202 0
      Strides-APP/app/components/Dropdown.js
  62. 145 0
      Strides-APP/app/components/ModalPortal.js
  63. 6 0
      Strides-APP/app/components/MyRefreshControl.js
  64. 74 0
      Strides-APP/app/components/TextRadius.js
  65. 79 0
      Strides-APP/app/components/Toolbar.js
  66. 0 0
      Strides-APP/app/components/countrys.json
  67. BIN
      Strides-APP/app/images/about-logo.png
  68. BIN
      Strides-APP/app/images/app-logo.png
  69. BIN
      Strides-APP/app/images/charge/bg-top-station.png
  70. BIN
      Strides-APP/app/images/charge/charge-complete.png
  71. BIN
      Strides-APP/app/images/charge/charge-item-select.png
  72. BIN
      Strides-APP/app/images/charge/ic-battery-0.png
  73. BIN
      Strides-APP/app/images/charge/ic-battery-1.png
  74. BIN
      Strides-APP/app/images/charge/ic-charge-circle.png
  75. BIN
      Strides-APP/app/images/charge/ic-payment.png
  76. BIN
      Strides-APP/app/images/charge/ic-plus-large.png
  77. BIN
      Strides-APP/app/images/charge/ic-plus-middle.png
  78. BIN
      Strides-APP/app/images/charge/ic-plus-small.png
  79. BIN
      Strides-APP/app/images/charge/ic-type-ac.png
  80. BIN
      Strides-APP/app/images/charge/ic-type-chademo.png
  81. BIN
      Strides-APP/app/images/charge/ic-type-dc.png
  82. BIN
      Strides-APP/app/images/charge/ic-type-rate.png
  83. BIN
      Strides-APP/app/images/charge/icon-station-no.png
  84. BIN
      Strides-APP/app/images/charge/icon-station.png
  85. BIN
      Strides-APP/app/images/charge/icon-type-interfaces.png
  86. BIN
      Strides-APP/app/images/charge/icon-type-stops.png
  87. BIN
      Strides-APP/app/images/country/AD.png
  88. BIN
      Strides-APP/app/images/country/AE.png
  89. BIN
      Strides-APP/app/images/country/AF.png
  90. BIN
      Strides-APP/app/images/country/AG.png
  91. BIN
      Strides-APP/app/images/country/AI.png
  92. BIN
      Strides-APP/app/images/country/AL.png
  93. BIN
      Strides-APP/app/images/country/AM.png
  94. BIN
      Strides-APP/app/images/country/AO.png
  95. BIN
      Strides-APP/app/images/country/AR.png
  96. BIN
      Strides-APP/app/images/country/AS.png
  97. BIN
      Strides-APP/app/images/country/AT.png
  98. BIN
      Strides-APP/app/images/country/AU.png
  99. BIN
      Strides-APP/app/images/country/AW.png
  100. BIN
      Strides-APP/app/images/country/AX.png

+ 6 - 0
Strides-APP/.buckconfig

@@ -0,0 +1,6 @@
+
+[android]
+  target = Google Inc.:Google APIs:23
+
+[maven_repositories]
+  central = https://repo1.maven.org/maven2

+ 3 - 0
Strides-APP/.editorconfig

@@ -0,0 +1,3 @@
+# Windows files
+[*.bat]
+end_of_line = crlf

+ 4 - 0
Strides-APP/.eslintrc.js

@@ -0,0 +1,4 @@
+module.exports = {
+  root: true,
+  extends: '@react-native-community',
+};

+ 66 - 0
Strides-APP/.flowconfig

@@ -0,0 +1,66 @@
+[ignore]
+; We fork some components by platform
+.*/*[.]android.js
+
+; Ignore "BUCK" generated dirs
+<PROJECT_ROOT>/\.buckd/
+
+; Ignore polyfills
+node_modules/react-native/Libraries/polyfills/.*
+
+; Flow doesn't support platforms
+.*/Libraries/Utilities/LoadingView.js
+
+[untyped]
+.*/node_modules/@react-native-community/cli/.*/.*
+
+[include]
+
+[libs]
+node_modules/react-native/interface.js
+node_modules/react-native/flow/
+
+[options]
+emoji=true
+
+esproposal.optional_chaining=enable
+esproposal.nullish_coalescing=enable
+
+exact_by_default=true
+
+module.file_ext=.js
+module.file_ext=.json
+module.file_ext=.ios.js
+
+munge_underscores=true
+
+module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
+module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
+
+suppress_type=$FlowIssue
+suppress_type=$FlowFixMe
+suppress_type=$FlowFixMeProps
+suppress_type=$FlowFixMeState
+
+[lints]
+sketchy-null-number=warn
+sketchy-null-mixed=warn
+sketchy-number=warn
+untyped-type-import=warn
+nonstrict-import=warn
+deprecated-type=warn
+unsafe-getters-setters=warn
+unnecessary-invariant=warn
+signature-verification-failure=warn
+
+[strict]
+deprecated-type
+nonstrict-import
+sketchy-null
+unclear-type
+unsafe-getters-setters
+untyped-import
+untyped-type-import
+
+[version]
+^0.137.0

+ 3 - 0
Strides-APP/.gitattributes

@@ -0,0 +1,3 @@
+# Windows files should use crlf line endings
+# https://help.github.com/articles/dealing-with-line-endings/
+*.bat text eol=crlf

+ 8 - 0
Strides-APP/.prettierrc.js

@@ -0,0 +1,8 @@
+module.exports = {
+  bracketSpacing: false,
+  jsxBracketSameLine: true,
+  singleQuote: true,
+  trailingComma: 'all',
+  arrowParens: 'avoid',
+  endOfLine: false,
+};

+ 1 - 0
Strides-APP/.watchmanconfig

@@ -0,0 +1 @@
+{}

+ 146 - 0
Strides-APP/App.js

@@ -0,0 +1,146 @@
+/**
+ * Sample React Native App
+ * https://github.com/facebook/react-native
+ *
+ * @format
+ * @flow strict-local
+ */
+
+import React from 'react';
+import type {Node} from 'react';
+import {
+  Alert,
+  Button,
+  SafeAreaView,
+  ScrollView,
+  StatusBar,
+  StyleSheet,
+  Text,
+  useColorScheme,
+  View,
+} from 'react-native';
+
+import {
+  Colors,
+  DebugInstructions,
+  Header,
+  LearnMoreLinks,
+  ReloadInstructions,
+} from 'react-native/Libraries/NewAppScreen';
+import Toolbar from './app/components/toolbar';
+import Dialog from './app/components/Dialog';
+
+const Section = ({children, title}): Node => {
+  const isDarkMode = useColorScheme() === 'dark';
+  return (
+    <View style={styles.sectionContainer}>
+      <Text
+        style={[
+          styles.sectionTitle,
+          {
+            color: isDarkMode ? Colors.white : Colors.black,
+          },
+        ]}>
+        {title}
+      </Text>
+      <Text
+        style={[
+          styles.sectionDescription,
+          {
+            color: isDarkMode ? Colors.light : Colors.dark,
+          },
+        ]}>
+        {children}
+      </Text>
+    </View>
+  );
+};
+
+const App: () => Node = () => {
+  const isDarkMode = useColorScheme() === 'dark';
+
+  const backgroundStyle = {
+    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
+  };
+
+  return (
+    <SafeAreaView style={backgroundStyle}>
+      <Toolbar/>
+      <ScrollView
+        contentInsetAdjustmentBehavior="automatic"
+        style={backgroundStyle}>
+        <Header />
+        <View
+          style={{
+            backgroundColor: isDarkMode ? Colors.black : Colors.white,
+          }}>
+          <Section title="Step One 1">
+            Edit <Text style={styles.highlight}>App.js</Text> to change this
+            screen and then come back to see your edits.
+          </Section>
+          <Button
+            style={{
+              height: 120,
+              backgroundColor: Colors.black,
+            }}
+            onPress={() => {
+              requestAnimationFrame(() => {
+                Dialog.showDialog({title:'邠心', message:'Hello World!', callback:() => {
+                  toastShort('试试就试试')
+                }});
+              });
+            }}
+            title="点我试试"
+          />
+          <View style={{padding: 16}}>
+            <Button onPress={() => {
+              console.log('navigator', navigator)
+              console.log('geolocation', navigator.geolocation)
+              this.navigator.geolocation.getCurrentPosition((location) => {
+                let latlng = {
+                  latitude: location.coords.latitude,
+                  longitude: location.coords.longitude,
+                  latitudeDelta: 0.0922,
+                  longitudeDelta: 0.0421,
+                }
+                Dialog.showDialog({title: 'Geolocation', message: JSON.stringify(latlng)})
+              })
+            }} title='定位'/>
+          </View>
+          
+          <Section title="See Your Changes">
+            <ReloadInstructions />
+          </Section>
+          <Section title="Debug">
+            <DebugInstructions />
+          </Section>
+          <Section title="Learn More">
+            Read the docs to discover what to do next:
+          </Section>
+          <LearnMoreLinks />
+        </View>
+      </ScrollView>
+    </SafeAreaView>
+  );
+};
+
+const styles = StyleSheet.create({
+  sectionContainer: {
+    marginTop: 32,
+    paddingHorizontal: 24,
+  },
+  sectionTitle: {
+    fontSize: 24,
+    fontWeight: '600',
+  },
+  sectionDescription: {
+    marginTop: 8,
+    fontSize: 18,
+    fontWeight: '400',
+  },
+  highlight: {
+    fontWeight: '700',
+  },
+});
+
+export default App;

+ 149 - 0
Strides-APP/README.md

@@ -0,0 +1,149 @@
+# ChargEco - React Native
+Application ID: com.strides.chargeco
+
+## 环境搭建
+`npm install -g yarn`  
+`yarn global add react-native-cli`  
+
+## 初始化
+`npx react-native init JuicePlus`
+
+## 安装库
+`yarn add @react-native-community/art`  
+`yarn add @react-native-community/cameraroll`  
+`yarn add @react-native-async-storage/async-storage`  
+`yarn add @react-native-community/checkbox`  
+`yarn add @react-native-community/geolocation`  
+`yarn add @react-native-community/masked-view`  
+`yarn add @react-native-community/push-notification-ios`  
+`yarn add @react-native-firebase/app`  
+`yarn add @react-native-firebase/messaging`  
+`yarn add @react-native-picker/picker`  
+`yarn add @react-navigation/drawer`  
+`yarn add @react-navigation/native`  
+`yarn add @react-navigation/stack`   
+
+`yarn add axios`  
+`yarn add react 17.0.1`  
+`yarn add react-native 0.64.0`  
+`yarn add react-native-camera`  
+`//yarn add react-native-cluster-map`  
+`yarn add react-native-device-info`  
+`yarn add react-native-gesture-handler`  
+`yarn add react-native-gradients`  
+`yarn add react-native-image-crop-picker`  
+`//yarn add react-native-image-picker`  
+`yarn add react-native-map-clustering`  
+`yarn add react-native-map-link`  
+`yarn add react-native-maps`  
+`yarn add react-native-modal`  
+`yarn add react-native-permissions`  
+`yarn add react-native-progress`  
+`yarn add react-native-push-notification`  
+`yarn add react-native-qrcode-scanner`  
+`yarn add react-native-reanimated`   
+`yarn add react-native-root-siblings`  
+`yarn add react-native-root-toast`  
+`yarn add react-native-safe-area-context`  
+`yarn add react-native-screens`  
+`yarn add react-native-share`  
+`yarn add react-native-svg`  
+`yarn add react-native-vector-icons`  
+`yarn add react-native-view-shot`  
+`yarn add react-native-webview`  
+`yarn add victory-native`  
+
+## 运行(快捷命令)
+>Debug Android (yarn android)  
+`react-native run-android`  
+
+>Debug iOS (yarn ios)  
+`react-native run-ios`  
+
+>Start Metro Server (yarn start)  
+`react-native start`  
+
+## 测试(快捷命令)
+>Test Release (yarn release)  
+`npx react-native run-android --variant=release`
+
+>Test jest (yarn test)  
+`npx jest`
+
+>Check ESLint (yarn lint)  
+`npx eslint .`
+
+## 清理(快捷命令)
+>Reset Caches (yarn reset)  
+`react-native start --reset-cache`
+
+>Clean Project (yarn clean)   
+`cd android & ./gradlew clean`
+
+## 打包(快捷命令)
+>Build AAB for Android (yarn bundle)  
+`cd android & ./gradlew bundleRelease`
+
+>Build APK (yarn apk)  
+`cd android & ./gradlew assembleRelease`
+
+>Build JS to Android (yarn build:android)  
+`react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/`
+
+>Build JS to iOS (yarn build:ios)  
+`react-native bundle --platform ios --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/`
+
+----------
+
+## 文档
+[React Native中文文档](https://reactnative.cn)  
+[导航库-React Navigation](https://reactnavigation.org)  
+[动画库-Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/)   
+[矢量图标集](https://oblador.github.io/react-native-vector-icons/)  
+[React Native常用库查询](https://reactnative.directory/)  
+[React Native教程集合](https://github.com/reactnativecn/react-native-guide)
+[React Native Firebase](https://rnfirebase.io/)
+
+## Library Used
+[地图库](https://github.com/react-native-maps/react-native-maps)  
+[定位服务](https://github.com/react-native-geolocation/react-native-geolocation)   
+[地图聚合ClusterMap](https://github.com/codempireio/react-native-cluster-map)   
+[地图聚合Clustering](https://github.com/venits/react-native-map-clustering)   
+[地图聚合Clustered](https://github.com/novalabio/react-native-maps-super-cluster)  
+[矢量图标库](https://github.com/oblador/react-native-vector-icons)  
+[Modal弹窗](https://github.com/jacklam718/react-native-modals)   
+[颜色渐变](https://github.com/FriendsOfReact/react-native-gradients)  
+[Camera相机](https://github.com/react-native-camera)   
+[二维码扫描](https://github.com/moaazsidat/react-native-qrcode-scanner)   
+[权限处理](https://github.com/zoontek/react-native-permissions)   
+[下拉选择](https://github.com/react-native-picker/picker)   
+[多选框](https://github.com/react-native-checkbox/react-native-checkbox)   
+[Toast消息提示](https://github.com/magicismight/react-native-root-toast)   
+[react-native-svg](https://github.com/react-native-svg/react-native-svg)   
+[react-native-progress](https://github.com/oblador/react-native-progress)  
+[react-native-screens](https://github.com/software-mansion/react-native-screens)  
+[react-native-root-siblings](https://github.com/magicismight/react-native-root-siblings)  
+[React Native Image Picker](https://github.com/react-native-image-picker/react-native-image-picker)  
+[React Native Image Crop Picker](https://github.com/ivpusic/react-native-image-crop-picker)  
+[Victory Native](https://formidable.com/open-source/victory/docs/native/)  
+[Push NOtification](https://github.com/zo0r/react-native-push-notification)  
+[push-notification-ios](https://github.com/react-native-push-notification-ios/push-notification-ios)  
+[react-native-view-shot](https://github.com/gre/react-native-view-shot)  
+[Cameraroll](https://github.com/react-native-cameraroll/react-native-cameraroll)  
+[React Native Share](https://react-native-share.github.io/react-native-share/)  
+[RNWebView](https://github.com/react-native-webview/react-native-webview)  
+[react-native-device-info](https://github.com/react-native-device-info/react-native-device-info)  
+
+
+## 文章
+https://blog.csdn.net/first_helloword/article/details/109903486  
+https://blog.csdn.net/LeoStarry2014/article/details/101178607  
+https://blog.csdn.net/daniel1227/article/details/95972996  
+https://blog.csdn.net/SpicyBoiledFish/article/details/70237681  
+https://www.jianshu.com/p/78fd5694446a  
+https://blog.csdn.net/qq_28483283/article/details/108239912  
+https://stackoverflow.com/questions/36685372/how-to-zoom-in-out-in-react-native-map  
+https://stackoverflow.com/questions/57227291/victory-native-events-props-not-working-in-react-native-event-not-firing-in/57723808  
+https://www.jianshu.com/p/0a51624dee2f  
+https://www.jianshu.com/p/8f75eb58b030  
+https://www.cnblogs.com/sundaysgarden/p/10357051.html

+ 55 - 0
Strides-APP/android/app/BUCK

@@ -0,0 +1,55 @@
+# To learn about Buck see [Docs](https://buckbuild.com/).
+# To run your application with Buck:
+# - install Buck
+# - `npm start` - to start the packager
+# - `cd android`
+# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
+# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
+# - `buck install -r android/app` - compile, install and run application
+#
+
+load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
+
+lib_deps = []
+
+create_aar_targets(glob(["libs/*.aar"]))
+
+create_jar_targets(glob(["libs/*.jar"]))
+
+android_library(
+    name = "all-libs",
+    exported_deps = lib_deps,
+)
+
+android_library(
+    name = "app-code",
+    srcs = glob([
+        "src/main/java/**/*.java",
+    ]),
+    deps = [
+        ":all-libs",
+        ":build_config",
+        ":res",
+    ],
+)
+
+android_build_config(
+    name = "build_config",
+    package = "com.evct.juiceplus",
+)
+
+android_resource(
+    name = "res",
+    package = "com.evct.juiceplus",
+    res = "src/main/res",
+)
+
+android_binary(
+    name = "app",
+    keystore = "//android/keystores:debug",
+    manifest = "src/main/AndroidManifest.xml",
+    package_type = "debug",
+    deps = [
+        ":app-code",
+    ],
+)

+ 274 - 0
Strides-APP/android/app/build.gradle

@@ -0,0 +1,274 @@
+apply plugin: "com.android.application"
+apply plugin: 'com.google.gms.google-services'
+
+import com.android.build.OutputFile
+
+def myVersionName = "2.2.1" //★★★★★版本号★★★★★
+/**
+ * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
+ * and bundleReleaseJsAndAssets).
+ * These basically call `react-native bundle` with the correct arguments during the Android build
+ * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
+ * bundle directly from the development server. Below you can see all the possible configurations
+ * and their defaults. If you decide to add a configuration block, make sure to add it before the
+ * `apply from: "../../node_modules/react-native/react.gradle"` line.
+ *
+ * project.ext.react = [
+ *   // the name of the generated asset file containing your JS bundle
+ *   bundleAssetName: "index.android.bundle",
+ *
+ *   // the entry file for bundle generation. If none specified and
+ *   // "index.android.js" exists, it will be used. Otherwise "index.js" is
+ *   // default. Can be overridden with ENTRY_FILE environment variable.
+ *   entryFile: "index.android.js",
+ *
+ *   // https://reactnative.dev/docs/performance#enable-the-ram-format
+ *   bundleCommand: "ram-bundle",
+ *
+ *   // whether to bundle JS and assets in debug mode
+ *   bundleInDebug: false,
+ *
+ *   // whether to bundle JS and assets in release mode
+ *   bundleInRelease: true,
+ *
+ *   // whether to bundle JS and assets in another build variant (if configured).
+ *   // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
+ *   // The configuration property can be in the following formats
+ *   //         'bundleIn${productFlavor}${buildType}'
+ *   //         'bundleIn${buildType}'
+ *   // bundleInFreeDebug: true,
+ *   // bundleInPaidRelease: true,
+ *   // bundleInBeta: true,
+ *
+ *   // whether to disable dev mode in custom build variants (by default only disabled in release)
+ *   // for example: to disable dev mode in the staging build type (if configured)
+ *   devDisabledInStaging: true,
+ *   // The configuration property can be in the following formats
+ *   //         'devDisabledIn${productFlavor}${buildType}'
+ *   //         'devDisabledIn${buildType}'
+ *
+ *   // the root of your project, i.e. where "package.json" lives
+ *   root: "../../",
+ *
+ *   // where to put the JS bundle asset in debug mode
+ *   jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
+ *
+ *   // where to put the JS bundle asset in release mode
+ *   jsBundleDirRelease: "$buildDir/intermediates/assets/release",
+ *
+ *   // where to put drawable resources / React Native assets, e.g. the ones you use via
+ *   // require('./image.png')), in debug mode
+ *   resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
+ *
+ *   // where to put drawable resources / React Native assets, e.g. the ones you use via
+ *   // require('./image.png')), in release mode
+ *   resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
+ *
+ *   // by default the gradle tasks are skipped if none of the JS files or assets change; this means
+ *   // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
+ *   // date; if you have any other folders that you want to ignore for performance reasons (gradle
+ *   // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
+ *   // for example, you might want to remove it from here.
+ *   inputExcludes: ["android/**", "ios/**"],
+ *
+ *   // override which node gets called and with what additional arguments
+ *   nodeExecutableAndArgs: ["node"],
+ *
+ *   // supply additional arguments to the packager
+ *   extraPackagerArgs: []
+ * ]
+ */
+
+project.ext.react = [
+    enableHermes: true,  // clean and rebuild if changing
+]
+
+apply from: "../../node_modules/react-native/react.gradle"
+apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
+
+/**
+ * Set this to true to create two separate APKs instead of one:
+ *   - An APK that only works on ARM devices
+ *   - An APK that only works on x86 devices
+ * The advantage is the size of the APK is reduced by about 4MB.
+ * Upload all the APKs to the Play Store and people will download
+ * the correct one based on the CPU architecture of their device.
+ */
+def enableSeparateBuildPerCPUArchitecture = false
+
+/**
+ * Run Proguard to shrink the Java bytecode in release builds.
+ */
+def enableProguardInReleaseBuilds = true
+
+/**
+ * The preferred build flavor of JavaScriptCore.
+ *
+ * For example, to use the international variant, you can use:
+ * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
+ *
+ * The international variant includes ICU i18n library and necessary data
+ * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
+ * give correct results when using with locales other than en-US.  Note that
+ * this variant is about 6MiB larger per architecture than default.
+ */
+def jscFlavor = 'org.webkit:android-jsc:+'
+
+/**
+ * Whether to enable the Hermes VM.
+ *
+ * This should be set on project.ext.react and mirrored here.  If it is not set
+ * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
+ * and the benefits of using Hermes will therefore be sharply reduced.
+ */
+def enableHermes = project.ext.react.get("enableHermes", false);
+
+def getMyVersionCode() {
+    def files = file('version.properties')
+    Properties props = new Properties()
+    props.load(new FileInputStream(files))
+    def code = props['VERSION_CODE'].toInteger()
+    //def task = gradle.startParameter.taskNames
+    //if ('assembleRelease' in task) {
+    code++
+    //}
+    props['VERSION_CODE'] = code.toString()
+    props.store(files.newWriter(), null)
+    return code
+}
+
+static def releaseTime() {
+    return new Date().format("yyyyMMdd", TimeZone.getDefault());
+}
+
+android {
+    ndkVersion rootProject.ext.ndkVersion
+
+    compileSdkVersion rootProject.ext.compileSdkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        applicationId "com.evct.juiceplus"
+        minSdkVersion rootProject.ext.minSdkVersion
+        targetSdkVersion rootProject.ext.targetSdkVersion
+        versionCode getMyVersionCode()
+        versionName myVersionName
+        multiDexEnabled true
+        missingDimensionStrategy 'react-native-camera', 'general'
+    }
+    splits {
+        abi {
+            reset()
+            enable enableSeparateBuildPerCPUArchitecture
+            universalApk false  // If true, also generate a universal APK
+            include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
+        }
+    }
+    signingConfigs {
+        debug {
+            storeFile file('vbea.keystore')
+            storePassword 'dec2020'
+            keyAlias 'vbea'
+            keyPassword 'dec2020'
+        }
+        release {
+            storeFile file('vbea.keystore')
+            storePassword 'dec2020'
+            keyAlias 'vbea'
+            keyPassword 'dec2020'
+        }
+    }
+
+    buildTypes {
+        debug {
+            signingConfig signingConfigs.debug
+        }
+        release {
+            // Caution! In production, you need to generate your own keystore file.
+            // see https://reactnative.dev/docs/signed-apk-android.
+            signingConfig signingConfigs.release
+            minifyEnabled enableProguardInReleaseBuilds
+            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+        }
+    }
+
+    // applicationVariants are e.g. debug, release
+    applicationVariants.all { variant ->
+        variant.outputs.each { output ->
+            // For each separate APK per architecture, set a unique version code as described here:
+            // https://developer.android.com/studio/build/configure-apk-splits.html
+            // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
+            def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
+            def abi = output.getFilter(OutputFile.ABI)
+            if (abi != null) {  // null for the universal-debug, universal-release variants
+                output.versionCodeOverride =
+                        defaultConfig.versionCode * 1000 + versionCodes.get(abi)
+            }
+
+        }
+        variant.outputs.all { output ->
+            if (variant.buildType.name=="release") {
+                def fileName = "Juice Plus-V${variant.versionName}-${variant.versionCode}"
+                // 加入打包时间
+                fileName = fileName + "-${releaseTime()}"
+                // 加入版本类型
+                fileName = fileName + "-${variant.buildType.name}.apk"
+                //output.outputFile = new File(outputFile.parent, fileName)
+                outputFileName = fileName
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    //noinspection GradleDynamicVersion
+    implementation "com.facebook.react:react-native:0.64.2"  // From node_modules
+    implementation 'com.facebook.fresco:animated-gif:2.0.0' // 如果你需要支持GIF动图
+    implementation "androidx.appcompat:appcompat:1.0.0"
+    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
+
+    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
+      exclude group:'com.facebook.fbjni'
+    }
+
+    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
+        exclude group:'com.facebook.flipper'
+        exclude group:'com.squareup.okhttp3', module:'okhttp'
+    }
+
+    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
+        exclude group:'com.facebook.flipper'
+    }
+
+    if (enableHermes) {
+        def hermesPath = "../../node_modules/hermes-engine/android/";
+        debugImplementation files(hermesPath + "hermes-debug.aar")
+        releaseImplementation files(hermesPath + "hermes-release.aar")
+    } else {
+        implementation jscFlavor
+    }
+    /*implementation(project(':react-native-maps')){
+       exclude group: 'com.google.android.gms', module: 'play-services-base'
+       exclude group: 'com.google.android.gms', module: 'play-services-maps'
+    }
+    implementation 'com.google.android.gms:play-services-base:17.2.1'*/
+    /*implementation 'com.google.android.gms:play-services-maps:17.0.0'
+    implementation 'com.google.maps.android:android-maps-utils:2.2.3'*/
+    implementation platform('com.google.firebase:firebase-bom:28.2.1')
+    implementation 'com.google.firebase:firebase-messaging'
+    implementation 'com.google.firebase:firebase-analytics'
+}
+
+// Run this once to be able to run the application with BUCK
+// puts all compile dependencies into folder libs for BUCK to use
+task copyDownloadableDepsToLibs(type: Copy) {
+    from configurations.compile
+    into 'libs'
+}
+
+apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

+ 19 - 0
Strides-APP/android/app/build_defs.bzl

@@ -0,0 +1,19 @@
+"""Helper definitions to glob .aar and .jar targets"""
+
+def create_aar_targets(aarfiles):
+    for aarfile in aarfiles:
+        name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
+        lib_deps.append(":" + name)
+        android_prebuilt_aar(
+            name = name,
+            aar = aarfile,
+        )
+
+def create_jar_targets(jarfiles):
+    for jarfile in jarfiles:
+        name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
+        lib_deps.append(":" + name)
+        prebuilt_jar(
+            name = name,
+            binary_jar = jarfile,
+        )

+ 39 - 0
Strides-APP/android/app/google-services.json

@@ -0,0 +1,39 @@
+{
+  "project_info": {
+    "project_number": "948303787961",
+    "project_id": "juiceplus-c5c61",
+    "storage_bucket": "juiceplus-c5c61.appspot.com"
+  },
+  "client": [
+    {
+      "client_info": {
+        "mobilesdk_app_id": "1:948303787961:android:c732f8811a2a248f9aa868",
+        "android_client_info": {
+          "package_name": "com.evct.juiceplus"
+        }
+      },
+      "oauth_client": [
+        {
+          "client_id": "948303787961-ns75dgg4v5va239b9ogs1t4qn914s5p5.apps.googleusercontent.com",
+          "client_type": 3
+        }
+      ],
+      "api_key": [
+        {
+          "current_key": "AIzaSyADWrNTAVnkyoyG9TG-xUHYcFB9q-siSEE"
+        }
+      ],
+      "services": {
+        "appinvite_service": {
+          "other_platform_oauth_client": [
+            {
+              "client_id": "948303787961-ns75dgg4v5va239b9ogs1t4qn914s5p5.apps.googleusercontent.com",
+              "client_type": 3
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "configuration_version": "1"
+}

+ 10 - 0
Strides-APP/android/app/proguard-rules.pro

@@ -0,0 +1,10 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:

+ 13 - 0
Strides-APP/android/app/src/debug/AndroidManifest.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
+    <application
+        android:usesCleartextTraffic="true"
+        tools:targetApi="28"
+        tools:ignore="GoogleAppIndexingWarning">
+        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
+    </application>
+</manifest>

+ 72 - 0
Strides-APP/android/app/src/debug/java/com/strides/chargeco/ReactNativeFlipper.java

@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * <p>This source code is licensed under the MIT license found in the LICENSE file in the root
+ * directory of this source tree.
+ */
+package com.strides.chargeco;
+
+import android.content.Context;
+import com.facebook.flipper.android.AndroidFlipperClient;
+import com.facebook.flipper.android.utils.FlipperUtils;
+import com.facebook.flipper.core.FlipperClient;
+import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
+import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
+import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
+import com.facebook.flipper.plugins.inspector.DescriptorMapping;
+import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
+import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
+import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
+import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
+import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.modules.network.NetworkingModule;
+import okhttp3.OkHttpClient;
+
+public class ReactNativeFlipper {
+  public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
+    if (FlipperUtils.shouldEnableFlipper(context)) {
+      final FlipperClient client = AndroidFlipperClient.getInstance(context);
+
+      client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
+      client.addPlugin(new ReactFlipperPlugin());
+      client.addPlugin(new DatabasesFlipperPlugin(context));
+      client.addPlugin(new SharedPreferencesFlipperPlugin(context));
+      client.addPlugin(CrashReporterPlugin.getInstance());
+
+      NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
+      NetworkingModule.setCustomClientBuilder(
+          new NetworkingModule.CustomClientBuilder() {
+            @Override
+            public void apply(OkHttpClient.Builder builder) {
+              builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
+            }
+          });
+      client.addPlugin(networkFlipperPlugin);
+      client.start();
+
+      // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
+      // Hence we run if after all native modules have been initialized
+      ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
+      if (reactContext == null) {
+        reactInstanceManager.addReactInstanceEventListener(
+            new ReactInstanceManager.ReactInstanceEventListener() {
+              @Override
+              public void onReactContextInitialized(ReactContext reactContext) {
+                reactInstanceManager.removeReactInstanceEventListener(this);
+                reactContext.runOnNativeModulesQueueThread(
+                    new Runnable() {
+                      @Override
+                      public void run() {
+                        client.addPlugin(new FrescoFlipperPlugin());
+                      }
+                    });
+              }
+            });
+      } else {
+        client.addPlugin(new FrescoFlipperPlugin());
+      }
+    }
+  }
+}

+ 109 - 0
Strides-APP/android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,109 @@
+<manifest
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.strides.chargeco">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
+    <application
+      android:name=".MainApplication"
+      android:label="@string/app_name"
+      android:icon="@mipmap/ic_launcher"
+      android:roundIcon="@mipmap/ic_launcher_round"
+      android:allowBackup="false"
+      android:theme="@style/AppTheme"
+      android:hardwareAccelerated="true"
+      android:usesCleartextTraffic="true"
+      android:requestLegacyExternalStorage="true">
+        <activity
+          android:name=".MainActivity"
+          android:label="@string/app_name"
+          android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+          android:launchMode="singleTask"
+          android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="juicep" />
+            </intent-filter>
+        </activity>
+        <!-- <provider
+          android:name="androidx.core.content.FileProvider"
+          android:authorities="${applicationId}.provider"
+          android:grantUriPermissions="true"
+          android:exported="false">
+            <meta-data
+              android:name="android.support.FILE_PROVIDER_PATHS"
+              android:resource="@xml/filepaths" />
+        </provider> -->
+        <!-- You will only need to add this meta-data tag, but make sure it's a child of application -->
+        <meta-data
+          android:name="com.google.android.geo.API_KEY"
+          android:value="@string/google_maps_key"/>
+        <!--meta-data
+          android:name="com.google.android.geo.API_KEY"
+          android:value="AIzaSyBAefhRlXEH3vCko-zZTX6PHllTR6av4WI"/-->
+        <!--meta-data
+            android:name="com.google.android.maps.v2.API_KEY"
+            android:value="@string/google_maps_key" /-->
+        <!-- Change the value to true to enable pop-up for in foreground on receiving remote notifications (for prevent duplicating while showing local notifications set this to false) -->
+        <meta-data 
+          android:name="com.dieam.reactnativepushnotification.notification_foreground"
+          android:value="true"/>
+        <!-- Change the resource name to your App's accent color - or any other color you want -->
+        <meta-data
+          android:name="com.dieam.reactnativepushnotification.notification_color"
+          android:resource="@color/white"/>
+        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
+        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
+        <receiver
+          android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
+                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
+            </intent-filter>
+        </receiver>
+        <!--service
+            android:name=".java.MyFirebaseMessagingService"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.google.firebase.MESSAGING_EVENT" />
+            </intent-filter>
+        </service-->
+        <service
+          android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
+          android:exported="false" >
+            <intent-filter>
+              <action android:name="com.google.firebase.MESSAGING_EVENT" />
+            </intent-filter>
+        </service>
+        <!-- 设置自定义默认图标。当没有为传入通知消息设置图标时,使用此选项。
+             See README(https://goo.gl/l4GJaQ) for more. -->
+        <meta-data
+          android:name="com.google.firebase.messaging.default_notification_icon"
+          android:resource="@drawable/ic_notification" />
+        <meta-data
+          android:name="com.google.firebase.messaging.default_notification_channel_id"
+          android:value="@string/default_notification_channel_id" />
+        <meta-data
+          android:name="com.dieam.reactnativepushnotification.default_notification_channel_id"
+          android:value="@string/default_notification_channel_id" />
+    </application>
+</manifest>

+ 22 - 0
Strides-APP/android/app/src/main/java/com/strides/chargeco/MainActivity.java

@@ -0,0 +1,22 @@
+package com.strides.chargeco;
+
+import android.os.Bundle;
+
+import com.facebook.react.ReactActivity;
+
+public class MainActivity extends ReactActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(null);
+    }
+    
+    /**
+     * Returns the name of the main component registered from JavaScript. This is used to schedule
+     * rendering of the component.
+     */
+    @Override
+    protected String getMainComponentName() {
+        return "JuicePlus";
+    }
+}

+ 90 - 0
Strides-APP/android/app/src/main/java/com/strides/chargeco/MainApplication.java

@@ -0,0 +1,90 @@
+package com.strides.chargeco;
+
+import android.app.Application;
+import android.content.Context;
+
+import com.facebook.react.PackageList;
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.ReactNativeHost;
+import com.facebook.react.ReactPackage;
+import com.facebook.soloader.SoLoader;
+
+import cl.json.ShareApplication;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+public class MainApplication extends Application implements ShareApplication, ReactApplication {
+
+  private final ReactNativeHost mReactNativeHost =
+      new ReactNativeHost(this) {
+        @Override
+        public boolean getUseDeveloperSupport() {
+          return BuildConfig.DEBUG;
+        }
+
+        @Override
+        protected List<ReactPackage> getPackages() {
+          @SuppressWarnings("UnnecessaryLocalVariable")
+          List<ReactPackage> packages = new PackageList(this).getPackages();
+          // Packages that cannot be autolinked yet can be added manually here, for example:
+          // packages.add(new MyReactNativePackage());
+          //packages.add(new ClusterMapPackage());
+          return packages;
+        }
+
+        @Override
+        protected String getJSMainModuleName() {
+          return "index";
+        }
+      };
+
+  @Override
+  public ReactNativeHost getReactNativeHost() {
+    return mReactNativeHost;
+  }
+
+  @Override
+  public void onCreate() {
+    super.onCreate();
+    SoLoader.init(this, /* native exopackage */ false);
+    initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
+  }
+
+  /**
+   * Loads Flipper in React Native templates. Call this in the onCreate method with something like
+   * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
+   *
+   * @param context
+   * @param reactInstanceManager
+   */
+  private static void initializeFlipper(
+      Context context, ReactInstanceManager reactInstanceManager) {
+    if (BuildConfig.DEBUG) {
+      try {
+        /*
+         We use reflection here to pick up the class that initializes Flipper,
+        since Flipper library is not available in release mode
+        */
+        Class<?> aClass = Class.forName("com.evct.juiceplus.ReactNativeFlipper");
+        aClass
+            .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
+            .invoke(null, context, reactInstanceManager);
+      } catch (ClassNotFoundException e) {
+        e.printStackTrace();
+      } catch (NoSuchMethodException e) {
+        e.printStackTrace();
+      } catch (IllegalAccessException e) {
+        e.printStackTrace();
+      } catch (InvocationTargetException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  @Override
+  public String getFileProviderAuthority() {
+      return BuildConfig.APPLICATION_ID + ".provider";
+  }
+}

BIN
Strides-APP/android/app/src/main/res/drawable/ic_notification.png


+ 24 - 0
Strides-APP/android/app/src/main/res/layout/marker_cluster.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:adjustViewBounds="true"
+        android:layout_gravity="center"
+        android:src="@drawable/ic_cluster"/>
+
+    <TextView
+        android:id="@id/amu_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingBottom="6dp"
+        android:textStyle="bold"
+        android:textSize="15sp"
+        android:textColor="#ffffff"
+        android:layout_gravity="center"/>
+
+</FrameLayout>

+ 24 - 0
Strides-APP/android/app/src/main/res/layout/marker_cluster_unavailable.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:adjustViewBounds="true"
+        android:layout_gravity="center"
+        android:src="@drawable/ic_cluster_un"/>
+
+    <TextView
+        android:id="@id/amu_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingBottom="6dp"
+        android:textStyle="bold"
+        android:textSize="15sp"
+        android:textColor="#aaaaaa"
+        android:layout_gravity="center"/>
+
+</FrameLayout>

+ 7 - 0
Strides-APP/android/app/src/main/res/layout/marker_site.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/image"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:adjustViewBounds="true"/>

BIN
Strides-APP/android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
Strides-APP/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
Strides-APP/android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
Strides-APP/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
Strides-APP/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
Strides-APP/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
Strides-APP/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
Strides-APP/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
Strides-APP/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
Strides-APP/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 6 - 0
Strides-APP/android/app/src/main/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="white">#FFFFFF</color>
+  <color name="colorPrimary">#FFCC2C</color>
+  <color name="colorAccent">#FFCC2C</color>
+</resources>

+ 5 - 0
Strides-APP/android/app/src/main/res/values/google_maps_api.xml

@@ -0,0 +1,5 @@
+<resources>
+  <string name="google_maps_key">AIzaSyAVzs860l2Iuu1zG80IT1Zu4w7OvbVmJ4g</string>
+  <string name="google_maps_key_bak">AIzaSyCHr28cWWH5eTBgXZoDT7ELm69NL_6GoGg</string>
+  <string name="google_maps_key2">AIzaSyBAefhRlXEH3vCko-zZTX6PHllTR6av4WI</string>
+</resources>

+ 4 - 0
Strides-APP/android/app/src/main/res/values/strings.xml

@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">JuicePlus</string>
+    <string name="default_notification_channel_id">10086</string>
+</resources>

+ 12 - 0
Strides-APP/android/app/src/main/res/values/styles.xml

@@ -0,0 +1,12 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="android:textColor">#000000</item>
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimary</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>

+ 5 - 0
Strides-APP/android/app/src/main/res/xml/filepaths.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+  <external-path name="external_files" path="."/>
+  <external-cache-path name="myexternalimages" path="Download/" />
+</paths>

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

@@ -0,0 +1,2 @@
+#Fri Nov 18 18:37:31 CST 2022
+VERSION_CODE=572

+ 44 - 0
Strides-APP/android/build.gradle

@@ -0,0 +1,44 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    ext {
+        buildToolsVersion = "30.0.3"
+        minSdkVersion = 21
+        compileSdkVersion = 30
+        targetSdkVersion = 30
+        playServicesVersion = "17.0.0"
+        firebaseMessagingVersion = "21.1.0"
+        ndkVersion = "20.1.5948944"
+    }
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath("com.android.tools.build:gradle:4.1.1")
+        classpath('com.google.gms:google-services:4.3.8')
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        mavenLocal()
+        maven {
+            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
+            url("$rootDir/../node_modules/react-native/android")
+        }
+        maven {
+            // Android JSC is installed from npm
+            url("$rootDir/../node_modules/jsc-android/dist")
+        }
+
+        google()
+        jcenter()
+        maven { url 'https://www.jitpack.io' }
+    }
+}
+
+project.ext.react = [
+    nodeExecutableAndArgs: ["node", "--max_old_space_size=8192"]
+]

+ 29 - 0
Strides-APP/android/gradle.properties

@@ -0,0 +1,29 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
+# Version of flipper SDK to use with React Native
+FLIPPER_VERSION=0.75.1
+org.gradle.jvmargs=-Xmx8192m

BIN
Strides-APP/android/gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
Strides-APP/android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 185 - 0
Strides-APP/android/gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
Strides-APP/android/gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 6 - 0
Strides-APP/android/settings.gradle

@@ -0,0 +1,6 @@
+rootProject.name = 'JuicePlus'
+apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
+applyNativeModulesSettingsGradle(settings)
+include ':app'
+// include ':react-native-maps'
+// project(':react-native-maps').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-maps/lib/android')

+ 8 - 0
Strides-APP/app.json

@@ -0,0 +1,8 @@
+{
+  "name": "JuicePlus",
+  "displayName": "Strides",
+  "versionCode": 10,
+  "versionName": "V0.0.1",
+  "product": false,
+  "debug": true
+}

+ 46 - 0
Strides-APP/app/api/apiCharge.js

@@ -0,0 +1,46 @@
+import { get, post } from "./http";
+
+const prefix = 'devicesApi/charge/';
+
+export default {
+  //打开时校验用户是否在充电中
+  checkIsCharging: (params) => {
+    return get(prefix + 'checkUserChargeStatus', params);
+  },
+  //扫码之后校验 connector 是否可用接口
+  checkQRStatus: (params) => {
+    return get(prefix + 'checkChargeStatus', params);
+  },
+  //调用开始充电接口
+  startCharge: (params) => {
+    return get(prefix + 'startCharge', params);
+  },
+  //调用结束充电接口
+  stopCharge: () => {
+    return get(prefix+ 'endCharge', {});
+  },
+  //结算接口
+  getChargeSummary: (params) => {
+    return get(prefix + 'getChargeSummary', params);
+  },
+  //获取充电接口状态的接口
+  getCurrentStatus: (params) => {
+    return get(prefix + 'checkConnectorStatus', params);
+  },
+  //查询用户是否正在充电
+  getUserCharging: () => {
+    return get(prefix + 'getUserCharging', {});
+  },
+  //预定充电接口
+  reserveCharge: (params) => {
+    return post(prefix + 'chargeReserve', params)
+  },
+  //检查用户是否有生效的预定
+  getUserReserve: (sitePk) => {
+    return get(prefix + 'checkUserReserve', { sitePk: sitePk })
+  },
+  //取消预定
+  cancelReserve: (reservePk) => {
+    return get(prefix + 'cancelReserve', { reservePk: reservePk });
+  }
+}

+ 18 - 0
Strides-APP/app/api/apiStation.js

@@ -0,0 +1,18 @@
+import { get, post } from "./http";
+
+const prefix = 'devicesApi/site/';
+
+export default {
+  getAllStation: (params) => {
+    return get(prefix + 'listSite', params);
+  },
+  searchStation: (params) => {
+    return get(prefix + 'searchSite', params);
+  },
+  getStationRate: (params) => {
+    return get(prefix + 'getSiteRate', params);
+  },
+  getNearestSite: (params) => {
+    return get(prefix + 'getNearestSite', params);
+  }
+}

+ 36 - 0
Strides-APP/app/api/apiUpload.js

@@ -0,0 +1,36 @@
+import { get, upload } from "./http";
+
+const prefix = 'devicesApi/picture/upload/';
+
+export default {
+  /**
+   * 上传图片
+   * @param {*} uri 图片地址
+   * @param {*} mime 图片类型
+   * @param {*} type 图片分类 ↓
+   * USER_PROFILE 用户头像
+   * FEEDBACK 用户反馈
+   * SERVICE_LOGO 服务提供商logo
+   * SITE 站点
+   * PDVL 驾驶证
+   * @returns 服务器路径
+   */
+  uploadImage(uri, mime, type) {
+    let data = new FormData();
+    data.append('file', {
+      uri: uri,
+      type: mime,
+      name: getFileName(uri)
+    });
+    return upload(prefix, data, {photoSubDir: type});
+  },
+  testNotification() {
+    return get('steve/devicesApi/googleNotification/doNotification');
+  }
+}
+
+const getFileName = (path) => {
+  var pos = path.lastIndexOf("/");
+  var name = path.substring(pos+1);
+  return name;
+}

+ 66 - 0
Strides-APP/app/api/apiUser.js

@@ -0,0 +1,66 @@
+import { get, post } from "./http";
+
+const prefix = 'devicesApi/user/';
+
+export default user = {
+  login: (param) => {
+    return post(prefix + 'login', param);
+  },
+  register: (param) => {
+    return post(prefix + 'register', param);
+  },
+  getConmpany: () => {
+    return get(prefix + 'getFleetCompanyList');
+  },
+  getProfile: () => {
+    return get(prefix + 'getUserDetail', {});
+  },
+  setProfile: (params) => {
+    return post(prefix + 'update', params);
+  },
+  setNotifySwitch: (params) => {
+    return post(prefix + 'updateUserNotifySwitch', params);
+  },
+  setNotifyToken: (token) => {
+    return post(prefix + 'registerGoogleToken', token);
+  },
+  getCountryCode: () => {
+    return get(prefix + 'getCountryCode', {});
+  },
+  rateCharge: (params) => {
+    return post(prefix + 'rating', params);
+  },
+  feedback: (params) => {
+    return post(prefix + 'feedback', params);
+  },
+  getTypeOfFeedback: () => {
+    return get(prefix + 'getTypeOfFeedback', {});
+  },
+  getVehicles: () => {
+    return get(prefix + 'getVehicles', {});
+  },
+  getVehicleById: (params) => {
+    return get(prefix + 'getVehicleById', params);
+  },
+  addVehicle: (params) => {
+    return post(prefix + 'addVehicle', params);
+  },
+  updateVehicle: (params) => {
+    return post(prefix + 'updateVehicle', params);
+  },
+  deleteVehicle: (params) => {
+    return get(prefix + 'delVehicleById', params);
+  },
+  getConnectorType: () => {
+    return get(prefix + 'getConnectorType', {});
+  },
+  sendVerificationCode: (email) => {
+    return get(prefix + 'sendVerificationCode', {email: email});
+  },
+  updatePassword: (params) => {
+    return post(prefix + 'updatePassword', params);
+  },
+  getCountryList: () => {
+    return get('devicesApi/base/getCountries')
+  }
+}

+ 30 - 0
Strides-APP/app/api/apiWallet.js

@@ -0,0 +1,30 @@
+import { get, post } from "./http";
+
+const prefix = 'devicesApi/wallet/';
+
+export default wallet = {
+  getTopUpAmountList: () => {
+    return get(prefix + 'getTopUpAmountList', {});
+  },
+  addCreditCard: (params) => {
+    return post(prefix + 'addCard', params);
+  },
+  getCardList: () => {
+    return get(prefix + 'getCardList', {});
+  },
+  getPayTypeList: () => {
+    return get(prefix + 'getPayWithList', {});
+  },
+  doPayment: (params) => {
+    return post(prefix + 'doPayment', params);
+  },
+  doPaymentV2: (params) => {
+    return post(prefix + 'v2/doPayment', params);
+  },
+  getOverviewData: () => {
+    return get(prefix + 'getTransactionsOverview', {});
+  },
+  getTransactionList: (params) => {
+    return get(prefix + 'getTransactionsHistory', params);
+  }
+}

+ 188 - 0
Strides-APP/app/api/http.js

@@ -0,0 +1,188 @@
+import Axios from 'axios';
+import { PageList } from '../pages/Router';
+import app from '../../app.json';
+
+//config
+//const hostUrl = 'http://161.117.183.142/';
+const hostUrl = app.product ? 'https://csms.evctechnology.com/' : 'http://161.117.183.142/';
+const service = app.product ? 'juiceplus/' : 'steve/'
+export const host = hostUrl;
+
+const DEBUG = app.debug && !app.product;
+
+Axios.defaults.timeout = 10000;
+Axios.interceptors.response.use((response) => {
+  if (DEBUG) {
+    console.log('-------', response.config.method, response.config.url);
+    console.log('-------', JSON.stringify(response.data));
+    console.log('-------', response.status);
+  }
+  if (response.data.code == '401') {
+    setAccessToken('');
+    startPage(PageList.login, {action: '401'});
+    return Promise.reject('Need sign in');
+  }
+  return response.data;
+}, (error) => {
+  console.info('-------error', error);
+  return Promise.reject(error);
+});
+
+export const get = (path, params) => {
+  return new Promise((resolve, reject) => {
+    Axios.get(host + service + path, {
+      params: params,
+      method: 'GET',
+      headers: {
+        'Accept': 'application/json',
+        'accessToken': global.accessToken ?? ''
+      }
+    }).then(res => {
+      if (res.success) {
+        resolve(res);
+      } else if (res.msg) {
+        reject(res.msg);
+      } else {
+        reject('Request Failed');
+      }
+    }).catch(err => {
+      console.info('HTTP-ERROR', err);
+      reject(err);
+    });
+  });
+}
+
+export const post = (path, params) => {
+  return new Promise((resolve, reject) => {
+    Axios.post(host + service + path, params, {
+      method: 'POST',
+      headers: {
+        'Accept': 'application/json',
+        'accessToken': global.accessToken ?? ''
+      }
+    }).then(res => {
+      if (res.success) {
+        resolve(res);
+      } else if (res.msg) {
+        reject(res.msg);
+      } else {
+        reject('Request Failed');
+      }
+    }).catch(err => {
+      console.info('HTTP-ERROR', err);
+      reject(err);
+    });
+  });
+}
+
+export const upload = (path, params, header) => {
+  return new Promise((resolve, reject) => {
+    Axios.post(host + service + path, params, {
+      method: 'POST',
+      headers: {
+        'Accept': 'application/json',
+        'Content-Type': 'multipart/form-data',
+        'accessToken': global.accessToken ?? '',
+        ...header
+      }
+    }).then(res => {
+      if (res.success) {
+        resolve(res);
+      } else if (res.msg) {
+        reject(res.msg);
+      } else {
+        reject('Request Failed');
+      }
+    }).catch(err => {
+      console.info('HTTP-ERROR', err);
+      reject(err);
+    });
+  })
+}
+
+export const GET = (url, params) => {
+  var request = host + service + url;
+  if (params) {
+    var keys = ''
+    for (let key in params) {
+      if (keys != '') {
+        keys += '&';
+      }
+      keys += key + '=' + encodeURI(params[key]);
+    }
+    if (keys !== '') {
+      request += '?' + keys;
+    }
+  }
+  return new Promise((resolve, reject) => {
+    fetch(request, {
+      method: 'GET',
+      headers: {
+        'Accept': 'application/json',
+        'accessToken': global.accessToken ?? ''
+      }.then((response) => {
+        if (response.ok) {
+          return response.json();
+        } else {
+          reject(response);
+        }
+      }).then(data => {
+        resolve(data);
+      }).catch((error) => {
+        reject(error);
+      })
+    });
+  });
+}
+
+export const POST = (url, params) => {
+  return new Promise((resolve, reject) => {
+    fetch(host + service + url, {
+      method: 'POST',
+      headers: {
+        'Accept': 'application/json',
+        'content-type': 'application/json',
+        'accessToken': global.accessToken ?? ''
+      },
+      body: JSON.stringify(params)
+    }).then((response) => {
+      if (response.ok) {
+        return response.json();
+      } else {
+        reject(response);
+      }
+    }).then(data => {
+      resolve(data);
+    }).catch((error) => {
+      reject(error);
+    })
+  });
+}
+
+export const SOCKET = (url) => {
+  const ws = new WebSocket(url); // like 'wss://vbea.com/path'
+
+  ws.onopen = () => {
+    // connection opened
+    ws.send('something'); // send a message
+  };
+
+  ws.onmessage = (e) => {
+    // a message was received
+    console.log(e.data);
+  };
+
+  ws.onerror = (e) => {
+    // an error occurred
+    console.log(e.message);
+  };
+
+  ws.onclose = (e) => {
+    // connection closed
+    console.log(e.code, e.reason);
+  };
+}
+
+export const setAccessToken = token => {
+  global.accessToken = token;
+}

+ 78 - 0
Strides-APP/app/components/Appbar.js

@@ -0,0 +1,78 @@
+import React from 'react';
+import { Image, Pressable, StyleSheet, View } from 'react-native';
+
+export const BackButton = ({navigation}) => {
+  return (
+    <Pressable
+      style={Styles.backIcon}
+      android_ripple = {{
+        color: '#909090',
+        radius: 22,
+        borderless: true
+      }}
+      onPress={() => {
+        goBack();
+      }}>
+      <BackIcon/>
+    </Pressable>
+  )
+}
+
+export const BackIcon = () => {
+  return <MaterialIcons name={isIOS ? 'arrow-back-ios' : 'arrow-back'} size={24} color={'#333333'} />
+}
+
+export const StationBack = ({bottom}) => {
+  return (
+    <Image
+      style={{
+        right: 0,
+        bottom: bottom ?? 24,
+        width: 125.8,
+        height: 137.64,
+        zIndex: 0,
+        position: 'absolute'
+      }}
+      source={require('../images/charge/bg-top-station.png')}/>
+  );
+}
+
+export const Styles = StyleSheet.create({
+  toolbar: {
+    height: 56,
+    paddingLeft: 2,
+    paddingRight: 2,
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: colorPrimary
+  },
+  backIcon: {
+    width: 48,
+    height: 48,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  content: {
+    flex: 1,
+    paddingRight: 48,
+    alignItems: 'center'
+  },
+  logo: {
+    width:123.8,
+    height: 38.95
+  }
+});
+
+export default Appbar = (props) => {
+  return (
+    <View style={Styles.toolbar}>
+      <BackButton {...props}/>
+      <View style={Styles.content}>
+        <Image
+          source={require('../images/tool-logo.png')}
+          style={Styles.logo}
+        />
+      </View>
+    </View>
+  )
+}

+ 39 - 0
Strides-APP/app/components/BottomModal.js

@@ -0,0 +1,39 @@
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
+import Modal from 'react-native-modal';
+
+export const ModalProps = {
+  avoidKeyboard: true,
+  animationIn: "fadeIn",
+  animationOut: "fadeOut",
+  propagateSwipe: true,
+  useNativeDriver: true,
+  hideModalContentWhileAnimating: true
+}
+
+export default BottomModal = ({visible=false, onHide, children}) => {
+  return (
+    <Modal
+      isVisible={visible}
+      onBackButtonPress={onHide}
+      onBackdropPress={onHide}
+      useNativeDriver={true}
+      style={styles.bottomModalView}>
+      <View style={styles.bottomModalContent}>
+        {children}
+      </View>
+    </Modal>
+  );
+}
+
+const styles = StyleSheet.create({
+  bottomModalView: {
+    margin: 0,
+    justifyContent: 'flex-end'
+  },
+  bottomModalContent: {
+    maxHeight: $vht(isIOS ? 92 : 96),
+    backgroundColor: 'white',
+    ...$borderRadius(20, 20, 0, 0)
+  },
+})

+ 209 - 0
Strides-APP/app/components/Button.js

@@ -0,0 +1,209 @@
+import React, { useEffect } from 'react';
+import { Pressable, StyleSheet, Text, View } from 'react-native';
+
+export default Button = ({
+  style = styles.buttonView,
+  text,
+  textSize,
+  textColor,
+  textStyle = styles.buttonText,
+  viewStyle = styles.button,
+  children,
+  onClick,
+  onLongClick,
+  disabled = false,
+  elevation = 0,
+  borderRadius = 4
+}) => {
+  var start = {}, end = {};
+  const isSamsung = BRAND == 'samsung';
+  return (
+    <View style={getElevation(style, disabled, elevation, borderRadius)}>
+      <Pressable
+        style={({pressed }) => [
+          pressed && isIOS && {
+            backgroundColor: 'rgba(0,0,0,.2)'
+          },
+          isIOS && getRadius(style, borderRadius),
+          viewStyle
+        ]}
+        disabled={disabled}
+        onPress={e => {
+          //if (!isSamsung && onClick) 
+          onClick(e)
+        }}
+        onLongPress={e => {
+          if (onLongClick) {
+            onLongClick(e)
+            start = {}
+          }
+        }}
+        onPressIn={(e) => {
+          /*if (isSamsung && e.nativeEvent)
+            start = e.nativeEvent*/
+          }
+        }
+        onPressOut={e => {
+          /*if (isSamsung && e.nativeEvent && start.timestamp) {
+            end = e.nativeEvent
+            var x = Math.abs(start.pageX - end.pageX)
+            var y = Math.abs(start.pageY - end.pageY)
+            //console.log(x, y, viewStyle.height)
+            if (x < 50 && y < 10) {
+              if (end.timestamp - start.timestamp > 600) { //长按
+                //console.log('LongClick')
+                if (onClick) onClick(e)
+              } else { //click
+                //console.log('Click')
+                if (onClick) onClick(e)
+              }
+            }
+          }*/
+        }}
+        android_ripple={ripple}>
+        { children ? children :
+          <Text style={mergeStyle(textStyle, textColor, textSize, disabled)}>{text}</Text>
+        }
+      </Pressable>
+    </View>
+  );
+}
+
+const mergeStyle = (style, color, size, disabled) => {
+  if (Array.isArray(style)) {
+    let res = {}
+    for (let s of style) {
+      res = {...res, ...s};
+    }
+    style = res;
+  }
+  var s = Object.assign({}, style);
+  if (disabled) {
+    s.color = '#999';
+  } else if (color) {
+    s.color = color;
+  }
+  if (size) {
+    s.fontSize = size;
+  }
+  return s;
+}
+
+const getRadius = (style, r) => {
+  if (Array.isArray(style)) {
+    let res = {}
+    for (let s of style) {
+      res = {...res, ...s};
+    }
+    style = res;
+  }
+  const s = {}
+  if (style.borderTopLeftRadius !== undefined)
+    s.borderTopLeftRadius = style.borderTopLeftRadius
+  if (style.borderTopRightRadius !== undefined)
+    s.borderTopRightRadius = style.borderTopRightRadius
+  if (style.borderBottomLeftRadius !== undefined)
+    s.borderBottomLeftRadius = style.borderBottomLeftRadius
+  if (style.borderBottomRightRadius !== undefined)
+    s.borderBottomRightRadius = style.borderBottomRightRadius
+  if (style.borderRadius !== undefined)
+    s.borderRadius = style.borderRadius
+  if (Object.keys(s).length == 0 && r) {
+    return $borderRadius(r)
+  }
+  return s;
+}
+
+const getElevation = (style, d, e, r) => {
+  if (Array.isArray(style)) {
+    let res = {}
+    for (let s of style) {
+      res = {...res, ...s};
+    }
+    style = res;
+  }
+  var s = Object.assign({}, style);
+  s.padding = 0;
+  if (!isIOS)
+    s.overflow = 'hidden';
+  s.flexDirection = 'row';
+  if (style.borderRadius == undefined && r) {
+    s.borderRadius = r;
+  }
+  if (!s.backgroundColor) {
+    s.backgroundColor = colorAccent
+  }
+  if (!d) {
+    var ele = e ? e : style.elevation ? style.elevation : 0
+    s = Object.assign(ElevationObject(ele), s)
+  } else {
+    s = Object.assign(ElevationObject(0), s)
+    s.backgroundColor = '#CCC';
+  }
+  return s;
+}
+
+export const ElevationObject = (e) => {
+  if (e) {
+    return {
+      elevation: e,
+      shadowOpacity: .5,
+      shadowColor: '#999999',
+      shadowOffset: {width: 0, height: e},
+    };
+  } else {
+    return {};
+  }
+}
+
+export const ViewHeight = (height) => {
+  return {
+    flex: 1,
+    height: height,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center',
+  }
+}
+
+export const ViewHeightPadding = (height, padding=0) => {
+  return {
+    flex: 1,
+    height: height,
+    paddingLeft: padding,
+    paddingRight: padding,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center',
+  }
+}
+
+const styles = StyleSheet.create({
+  buttonView: {
+    backgroundColor: colorAccent
+  },
+  button: {
+    flex: 1,
+    height: 50,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center',
+  },
+  buttonMini: {
+    height: 42,
+    borderRadius: 6,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: colorAccent
+  },
+  buttonText: {
+    color: '#333',
+    fontSize: 16,
+    fontWeight: 'bold',
+    textAlign: 'center',
+  }
+});

+ 41 - 0
Strides-APP/app/components/CheckBox.js

@@ -0,0 +1,41 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import CheckBoxBase from '@react-native-community/checkbox';
+
+const CheckBox = ({
+  value=false,
+  disabled=false,
+  onValueChange,
+  iosSize=24
+}) => {
+  if (isIOS) {
+    return (
+      <MaterialIcons
+        name={value ? "check-box" : "check-box-outline-blank"}
+        size={iosSize}
+        color={value ? colorAccent : '#777'}
+        style={styles.checkBoxView}
+        onPress={() => {
+          if (onValueChange)
+            onValueChange(!value)
+        }}
+      />
+    );
+  } else {
+    return (
+      <CheckBoxBase
+        value={value}
+        disabled={disabled}
+        onValueChange={onValueChange}
+      />
+    )
+  }
+}
+
+const styles = StyleSheet.create({
+  checkBoxView: {
+    padding: 4
+  }
+})
+
+export default CheckBox;

+ 45 - 0
Strides-APP/app/components/CheckBoxText.js

@@ -0,0 +1,45 @@
+import React from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+import CheckBox from '../components/CheckBox';
+
+const CheckBoxText = (
+  {
+    value=false,
+    text='',
+    disabled=false,
+    onValueChange,
+    flexText=false,
+    style=styles.checkboxItem,
+    textStyle=styles.checkboxText
+  }
+) => (
+  <View style={style}>
+    <CheckBox
+      value={value}
+      disabled={disabled}
+      onValueChange={onValueChange}/>
+    <Text
+      style={[textStyle, flexText ? {flex: 1} : {}]}
+      onPress={() => {
+        if (onValueChange) onValueChange(!value)
+      }}>{text}</Text>
+  </View>
+)
+
+export default CheckBoxText;
+
+const styles = StyleSheet.create({
+  checkboxItem: {
+    flex: 1,
+    minWidth: 100,
+    paddingTop: 2,
+    paddingBottom: 4,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  checkboxText: {
+    color: '#222',
+    fontSize: 16,
+    padding: 4
+  }
+})

+ 496 - 0
Strides-APP/app/components/CountryIcon.js

@@ -0,0 +1,496 @@
+/**
+ * 国家图标组件
+ * @邠心vbe on 2021/10/08
+ */
+import React from 'react';
+import { Image, StyleSheet, Text } from 'react-native';
+import countries from './countrys.json';
+import Button from './Button';
+import apiUser from '../api/apiUser';
+
+const RNCountryList = {
+  AD: require('../images/country/AD.png'),
+  AE: require('../images/country/AE.png'),
+  AF: require('../images/country/AF.png'),
+  AG: require('../images/country/AG.png'),
+  AI: require('../images/country/AI.png'),
+  AL: require('../images/country/AL.png'),
+  AM: require('../images/country/AM.png'),
+  AO: require('../images/country/AO.png'),
+  AR: require('../images/country/AR.png'),
+  AS: require('../images/country/AS.png'),
+  AT: require('../images/country/AT.png'),
+  AU: require('../images/country/AU.png'),
+  AW: require('../images/country/AW.png'),
+  AX: require('../images/country/AX.png'),
+  AZ: require('../images/country/AZ.png'),
+  BA: require('../images/country/BA.png'),
+  BB: require('../images/country/BB.png'),
+  BD: require('../images/country/BD.png'),
+  BE: require('../images/country/BE.png'),
+  BF: require('../images/country/BF.png'),
+  BG: require('../images/country/BG.png'),
+  BH: require('../images/country/BH.png'),
+  BI: require('../images/country/BI.png'),
+  BJ: require('../images/country/BJ.png'),
+  BL: require('../images/country/BL.png'),
+  BM: require('../images/country/BM.png'),
+  BN: require('../images/country/BN.png'),
+  BO: require('../images/country/BO.png'),
+  BQ: require('../images/country/BQ.png'),
+  BR: require('../images/country/BR.png'),
+  BS: require('../images/country/BS.png'),
+  BT: require('../images/country/BT.png'),
+  BW: require('../images/country/BW.png'),
+  BY: require('../images/country/BY.png'),
+  BZ: require('../images/country/BZ.png'),
+  CA: require('../images/country/CA.png'),
+  CC: require('../images/country/CC.png'),
+  CD: require('../images/country/CD.png'),
+  CF: require('../images/country/CF.png'),
+  CG: require('../images/country/CG.png'),
+  CH: require('../images/country/CH.png'),
+  CI: require('../images/country/CI.png'),
+  CK: require('../images/country/CK.png'),
+  CL: require('../images/country/CL.png'),
+  CM: require('../images/country/CM.png'),
+  CN: require('../images/country/CN.png'),
+  CO: require('../images/country/CO.png'),
+  CR: require('../images/country/CR.png'),
+  CU: require('../images/country/CU.png'),
+  CV: require('../images/country/CV.png'),
+  CW: require('../images/country/CW.png'),
+  CX: require('../images/country/CX.png'),
+  CY: require('../images/country/CY.png'),
+  CZ: require('../images/country/CZ.png'),
+  DE: require('../images/country/DE.png'),
+  DJ: require('../images/country/DJ.png'),
+  DK: require('../images/country/DK.png'),
+  DM: require('../images/country/DM.png'),
+  DO: require('../images/country/DO.png'),
+  DZ: require('../images/country/DZ.png'),
+  EC: require('../images/country/EC.png'),
+  EE: require('../images/country/EE.png'),
+  EG: require('../images/country/EG.png'),
+  EH: require('../images/country/EH.png'),
+  ER: require('../images/country/ER.png'),
+  ES: require('../images/country/ES.png'),
+  ET: require('../images/country/ET.png'),
+  FI: require('../images/country/FI.png'),
+  FJ: require('../images/country/FJ.png'),
+  FK: require('../images/country/FK.png'),
+  FM: require('../images/country/FM.png'),
+  FO: require('../images/country/FO.png'),
+  FR: require('../images/country/FR.png'),
+  GA: require('../images/country/GA.png'),
+  GB: require('../images/country/GB.png'),
+  GD: require('../images/country/GD.png'),
+  GE: require('../images/country/GE.png'),
+  GF: require('../images/country/GF.png'),
+  GG: require('../images/country/GG.png'),
+  GH: require('../images/country/GH.png'),
+  GI: require('../images/country/GI.png'),
+  GM: require('../images/country/GM.png'),
+  GN: require('../images/country/GN.png'),
+  GP: require('../images/country/GP.png'),
+  GQ: require('../images/country/GQ.png'),
+  GR: require('../images/country/GR.png'),
+  GT: require('../images/country/GT.png'),
+  GU: require('../images/country/GU.png'),
+  GW: require('../images/country/GW.png'),
+  GY: require('../images/country/GY.png'),
+  HK: require('../images/country/HK.png'),
+  HN: require('../images/country/HN.png'),
+  HR: require('../images/country/HR.png'),
+  HT: require('../images/country/HT.png'),
+  HU: require('../images/country/HU.png'),
+  ID: require('../images/country/ID.png'),
+  IE: require('../images/country/IE.png'),
+  IL: require('../images/country/IL.png'),
+  IM: require('../images/country/IM.png'),
+  IN: require('../images/country/IN.png'),
+  IO: require('../images/country/IO.png'),
+  IQ: require('../images/country/IQ.png'),
+  IR: require('../images/country/IR.png'),
+  IS: require('../images/country/IS.png'),
+  IT: require('../images/country/IT.png'),
+  JE: require('../images/country/JE.png'),
+  JM: require('../images/country/JM.png'),
+  JO: require('../images/country/JO.png'),
+  JP: require('../images/country/JP.png'),
+  KE: require('../images/country/KE.png'),
+  KG: require('../images/country/KG.png'),
+  KH: require('../images/country/KH.png'),
+  KI: require('../images/country/KI.png'),
+  KM: require('../images/country/KM.png'),
+  KN: require('../images/country/KN.png'),
+  KP: require('../images/country/KP.png'),
+  KR: require('../images/country/KR.png'),
+  KS: require('../images/country/KS.png'),
+  KW: require('../images/country/KW.png'),
+  KY: require('../images/country/KY.png'),
+  KZ: require('../images/country/KZ.png'),
+  LA: require('../images/country/LA.png'),
+  LB: require('../images/country/LB.png'),
+  LC: require('../images/country/LC.png'),
+  LI: require('../images/country/LI.png'),
+  LK: require('../images/country/LK.png'),
+  LR: require('../images/country/LR.png'),
+  LS: require('../images/country/LS.png'),
+  LT: require('../images/country/LT.png'),
+  LU: require('../images/country/LU.png'),
+  LV: require('../images/country/LV.png'),
+  LY: require('../images/country/LY.png'),
+  MA: require('../images/country/MA.png'),
+  MC: require('../images/country/MC.png'),
+  MD: require('../images/country/MD.png'),
+  ME: require('../images/country/ME.png'),
+  MF: require('../images/country/MF.png'),
+  MG: require('../images/country/MG.png'),
+  MH: require('../images/country/MH.png'),
+  MK: require('../images/country/MK.png'),
+  ML: require('../images/country/ML.png'),
+  MM: require('../images/country/MM.png'),
+  MN: require('../images/country/MN.png'),
+  MO: require('../images/country/MO.png'),
+  MP: require('../images/country/MP.png'),
+  MQ: require('../images/country/MQ.png'),
+  MR: require('../images/country/MR.png'),
+  MS: require('../images/country/MS.png'),
+  MT: require('../images/country/MT.png'),
+  MU: require('../images/country/MU.png'),
+  MV: require('../images/country/MV.png'),
+  MW: require('../images/country/MW.png'),
+  MX: require('../images/country/MX.png'),
+  MY: require('../images/country/MY.png'),
+  MZ: require('../images/country/MZ.png'),
+  NA: require('../images/country/NA.png'),
+  NC: require('../images/country/NC.png'),
+  NE: require('../images/country/NE.png'),
+  NF: require('../images/country/NF.png'),
+  NG: require('../images/country/NG.png'),
+  NI: require('../images/country/NI.png'),
+  NL: require('../images/country/NL.png'),
+  NO: require('../images/country/NO.png'),
+  NP: require('../images/country/NP.png'),
+  NR: require('../images/country/NR.png'),
+  NU: require('../images/country/NU.png'),
+  NZ: require('../images/country/NZ.png'),
+  OM: require('../images/country/OM.png'),
+  PA: require('../images/country/PA.png'),
+  PE: require('../images/country/PE.png'),
+  PF: require('../images/country/PF.png'),
+  PG: require('../images/country/PG.png'),
+  PH: require('../images/country/PH.png'),
+  PK: require('../images/country/PK.png'),
+  PL: require('../images/country/PL.png'),
+  PM: require('../images/country/PM.png'),
+  PR: require('../images/country/PR.png'),
+  PS: require('../images/country/PS.png'),
+  PT: require('../images/country/PT.png'),
+  PW: require('../images/country/PW.png'),
+  PY: require('../images/country/PY.png'),
+  QA: require('../images/country/QA.png'),
+  RE: require('../images/country/RE.png'),
+  RO: require('../images/country/RO.png'),
+  RS: require('../images/country/RS.png'),
+  RU: require('../images/country/RU.png'),
+  RW: require('../images/country/RW.png'),
+  SA: require('../images/country/SA.png'),
+  SB: require('../images/country/SB.png'),
+  SC: require('../images/country/SC.png'),
+  SD: require('../images/country/SD.png'),
+  SE: require('../images/country/SE.png'),
+  SG: require('../images/country/SG.png'),
+  SH: require('../images/country/SH.png'),
+  SI: require('../images/country/SI.png'),
+  SJ: require('../images/country/SJ.png'),
+  SK: require('../images/country/SK.png'),
+  SL: require('../images/country/SL.png'),
+  SM: require('../images/country/SM.png'),
+  SN: require('../images/country/SN.png'),
+  SO: require('../images/country/SO.png'),
+  SR: require('../images/country/SR.png'),
+  SS: require('../images/country/SS.png'),
+  ST: require('../images/country/ST.png'),
+  SV: require('../images/country/SV.png'),
+  SX: require('../images/country/SX.png'),
+  SY: require('../images/country/SY.png'),
+  SZ: require('../images/country/SZ.png'),
+  TC: require('../images/country/TC.png'),
+  TD: require('../images/country/TD.png'),
+  TG: require('../images/country/TG.png'),
+  TH: require('../images/country/TH.png'),
+  TJ: require('../images/country/TJ.png'),
+  TK: require('../images/country/TK.png'),
+  TL: require('../images/country/TL.png'),
+  TM: require('../images/country/TM.png'),
+  TN: require('../images/country/TN.png'),
+  TO: require('../images/country/TO.png'),
+  TR: require('../images/country/TR.png'),
+  TT: require('../images/country/TT.png'),
+  TV: require('../images/country/TV.png'),
+  TW: require('../images/country/TW.png'),
+  TZ: require('../images/country/TZ.png'),
+  UA: require('../images/country/UA.png'),
+  UG: require('../images/country/UG.png'),
+  US: require('../images/country/US.png'),
+  UY: require('../images/country/UY.png'),
+  UZ: require('../images/country/UZ.png'),
+  VA: require('../images/country/VA.png'),
+  VC: require('../images/country/VC.png'),
+  VE: require('../images/country/VE.png'),
+  VG: require('../images/country/VG.png'),
+  VI: require('../images/country/VI.png'),
+  VN: require('../images/country/VN.png'),
+  VU: require('../images/country/VU.png'),
+  WF: require('../images/country/WF.png'),
+  WS: require('../images/country/WS.png'),
+  YE: require('../images/country/YE.png'),
+  YT: require('../images/country/YT.png'),
+  ZA: require('../images/country/ZA.png'),
+  ZM: require('../images/country/ZM.png'),
+  ZW: require('../images/country/ZW.png')
+}
+
+//const RNCurrencyList = ['SG', 'US', 'MY', 'HK', 'AU', 'CA', 'PH', 'ID', 'NZ', 'JP']
+
+export const CountryIcon = (
+  {
+    style={},
+    width=24,
+    height=16,
+    borderRadius=2,
+    countryCode='SG'
+  }
+) => (
+  <Image
+    style={
+      {
+        width: width, 
+        height: height,
+        borderRadius: borderRadius,
+        ...style //Any image styles
+      }
+    }
+    source={
+      //**SG
+      RNCountryList[countryCode ?? 'SG']
+    }
+  />
+)
+
+export const GetCountryList = (back) => {
+  if (global.ComCountryList === undefined) {
+    console.log('--------------------------------------------------------');
+    console.log('--START--', 'GetCountryList');
+    const list = []
+    //const curl = []
+    //console.log('原始国家', countries.length);
+    apiUser.getCountryList().then(res => {
+      if (res.data && res.data.length) {
+        res.data.forEach(item => {
+          const country = {
+            countryNum: item.callingCode,
+            countryCode: item.countryCode,
+            countryName: item.countryName,
+            currency: item.currency,
+            currencySign: item.currencySymbol
+          };
+          list.push(country);
+        })
+        if (list.length > 0) {
+          global.ComCountryList = list;
+          if (back) back(global.ComCountryList);
+        }
+      }
+    }).catch(err => {
+      countries.forEach(item => {
+        if (RNCountryList[item.countryCode] !== undefined && item.region !== 'Antarctic' && item.subregion !== 'Polynesia' && !item.exclude) {
+          //item.subregion === 'Eastern Asia' || item.subregion === 'South-Eastern Asia') {
+          const country = {
+            countryNum: item.callingCode[0],
+            countryCode: item.countryCode,
+            countryName: item.countryName,
+            currencySign: item.currencySign,
+            currency: item.currency[0]
+          };
+          list.push(country);
+          /*if (RNCurrencyList.indexOf(item.countryCode) >= 0) {
+            curl.push(country);
+          }*/
+        }
+      })
+      //console.log('最终国家', list.length);
+      if (list.length > 0) {
+        global.ComCountryList = list;
+        //global.CurCountryList = curl;
+        if (back) back(global.ComCountryList);
+      }
+    });
+  } else {
+    if (back) back(global.ComCountryList);
+  }
+}
+
+export const GetCurrencyCountryList = (back) => {
+  if (global.CurCountryList === undefined) {
+    GetCountryList(list => {
+      if (back) back(global.CurCountryList);
+    })
+  } else {
+    if (back) back(global.CurCountryList);
+  }
+}
+
+/**
+ * 根据国家代码查询国家信息
+ * @param {*} countryCode 国家代码
+ * @returns 国家信息
+ */
+export const GetCountryByCode = (countryCode, back) => {
+  if (countryCode) {
+    GetCountryList(list => {
+      for (let item of list) {
+        //console.log(item.countryCode, item.countryCode === countryCode);
+        if (item.countryCode === countryCode) {
+          if (back) back(item);
+          break;
+        }
+      }
+    })
+  } else {
+    if (back) back(undefined);
+  }
+}
+
+/**
+ * 根据国家代码查询国家信息
+ * @param {*} countryCode 国家代码
+ * @returns 国家信息
+ */
+ export const GetCountryByNum = (countryNum, back) => {
+  if (global.ComCountryList && countryNum) {
+    for (let item of global.ComCountryList) {
+      //console.log(item.countryNum, item.countryNum === countryNum);
+      if (item.countryNum === countryNum) {
+        if (back) back(item);
+        break;
+      }
+    }
+  } else {
+    if (back) back(undefined);
+  }
+}
+
+/**
+ * CallingCode下拉框组件
+ * @param {*} param0 国家信息{country, value, onClick}
+ * @returns 下拉框组件
+ */
+export const CountryDropNum = ({country, value, onClick}) => {
+  return (
+    <Button
+      style={styles.countryDropItem}
+      viewStyle={styles.countryDropItemView}
+      onClick={onClick}>
+      <CountryIcon
+        borderRadius={0}
+        style={styles.iconBorder}
+        countryCode={country.countryCode}/>
+      <Text style={styles.countryDropItemText}>{country.countryName} ({'+'+country.countryNum})</Text>
+      { value == country.countryNum &&
+        <MaterialIcons
+          name='check-circle'
+          color={colorAccent}
+          size={20}/>
+      }
+    </Button>
+  )
+}
+
+/**
+ * 国家名称下拉框组件
+ * @param {*} param0 国家信息{country, value, onClick}
+ * @returns 下拉框组件
+ */
+export const CountryDropCode = ({country, value, onClick}) => {
+  return (
+    <Button
+      style={styles.countryDropItem}
+      viewStyle={styles.countryDropItemView}
+      onClick={onClick}>
+      <CountryIcon
+        style={styles.iconBorder}
+        borderRadius={0}
+        countryCode={country.countryCode}/>
+      <Text style={styles.countryDropItemText}>{country.countryName}</Text>
+      { value == country.countryCode &&
+        <MaterialIcons
+          name='check-circle'
+          color={colorAccent}
+          size={20}/>
+      }
+    </Button>
+  )
+}
+
+/**
+ * 货币符号下拉框组件
+ * @param {*} param0 国家信息{country, value, onClick}
+ * @returns 下拉框组件
+ */
+export const CountryDropCurrency = ({country, value, onClick}) => {
+  return (
+    <Button
+      style={styles.countryDropItem}
+      viewStyle={styles.countryDropItemView}
+      onClick={onClick}>
+      <CountryIcon
+        style={styles.iconBorder}
+        borderRadius={0}
+        countryCode={country.countryCode}/>
+      <Text style={styles.countryDropItemText}>{country.countryName} ({country.currencySign})</Text>
+      { value == country.countryCode
+      ? <MaterialIcons
+          name='check-circle'
+          color={colorAccent}
+          size={20}/>
+      : <Text style={styles.countryExchangeRate}>{country.exchangeRate}</Text>
+      }
+    </Button>
+  )
+}
+
+const styles = StyleSheet.create({
+  countryDropItem: {
+    borderRadius: 0,
+    backgroundColor: 'white'
+  },
+  countryDropItemView: {
+    flex: 1,
+    height: 50,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  countryDropItemText: {
+    flex: 1,
+    color: '#333',
+    fontSize: 14,
+    paddingLeft: 16
+  },
+  countryExchangeRate: {
+    color: '#999',
+    fontSize: 10,
+  },
+  iconBorder: {
+    borderWidth: 1,
+    borderColor: 'rgba(100, 100, 100, .1)'
+  },
+  payIcon: {
+    width: 50,
+    height: 25
+  }
+})

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

@@ -0,0 +1,446 @@
+/**
+ * Dialog
+ * @邠心vbe on 2021/02/20
+ */
+import React from 'react';
+import * as Progress from 'react-native-progress';
+import {Pressable, StyleSheet, Text, View, Image} from 'react-native';
+import Toast from 'react-native-root-toast';
+import utils from '../utils/utils';
+import ModalPortal from './ModalPortal';
+import Button from './Button';
+
+const maxDef = isIOS ? 480 : 540;
+var _maxWidth = isIOS ? $vw(75) : $vw(87);
+const maxWidth = _maxWidth > maxDef ? maxDef: _maxWidth;
+
+const BUTTON_OK = 'ok';
+const BUTTON_CANCEL = 'cancel';
+
+export const getDialogWidth = () => {
+  return maxWidth;
+}
+
+/**
+ * 显示一个弹窗
+ * @param {*} props 参数{title, message, ok, cancel, showCancel, callback(button)}
+ */
+const showDialog = (props) => {
+  var param = {
+    align: props.align || 'left',
+    title: props.title || '',
+    message: props.message || '',
+    ok: props.ok || 'OK',
+    cancel: props.cancel || 'CANCEL',
+    showCancel: props.showCancel != undefined ? props.showCancel : true,
+    callback: props.callback
+  }
+  ModalPortal.show((
+    isIOS ? <IOSDialog {...param}/>
+          : <AndroidDialog {...param}/>
+  ));
+}
+
+/**
+ * 显示一个只有确认按钮的弹窗
+ * @param {}} message 消息
+ * @param {*} ok 按钮文字
+ * @param {*} back callback(btn)
+ */
+const showResultDialog = (message, ok, back) => {
+  var param = {
+    title: isIOS ? message : '',
+    message: !isIOS ? message : '',
+    showCancel: false,
+    ok: ok || 'I Known',
+    callback: back
+  }
+  showDialog(param);
+}
+
+const showProgressDialog = (message='Loading...') => {
+  //message = message ?? 'Waiting...';
+  ModalPortal.showLoading((
+    isIOS ? <IOSProgress message={message}/>
+          : <AndroidProgress message={message}/>
+  ));
+}
+
+const dismissDialog = () => {
+  ModalPortal.dismiss();
+}
+
+const dismissLoading = () => {
+  ModalPortal.dismissLoading();
+}
+
+const dismissAll = () => {
+  ModalPortal.dismissAll();
+}
+
+const IOSDialog = (props) => {
+  return (
+    <View style={iosStyle.modalDialog}>
+      { props.title != '' &&
+        <Text style={iosStyle.modalTitle}>{props.title}</Text>
+      }
+      { props.message != '' &&
+        <Text style={[
+          iosStyle.message,
+          {
+            textAlign: props.align
+          }
+        ]}>
+          {props.message}
+        </Text>
+      }
+      <View style={iosStyle.modalFooter}>
+        <Button
+          text={props.ok}
+          style={iosStyle.btnGroup}
+          viewStyle={iosStyle.btnView}
+          textStyle={iosStyle.btnConfirm}
+          onClick={() => {
+            dismissDialog();
+            if (props.callback) {
+              props.callback(BUTTON_OK);
+            }
+          }}/>
+        { props.showCancel &&
+          <Button
+            text={props.cancel}
+            style={[iosStyle.btnGroup, iosStyle.btnRight]}
+            viewStyle={iosStyle.btnView}
+            textStyle={iosStyle.btnText}
+            onClick={() => {
+              dismissDialog();
+              if (props.callback) {
+                props.callback(BUTTON_CANCEL);
+              }
+            }}/>
+        }
+      </View>
+    </View>
+  );
+}
+
+const AndroidDialog = (props) => {
+  return (
+    <View style={andStyles.modalDialog}>
+      { props.title != '' &&
+        <Text style={andStyles.title}>
+          {props.title}
+        </Text>
+      }
+      { props.message != '' &&
+        <Text style={[
+          andStyles.message,
+          {
+            textAlign: props.align
+          }
+        ]}>
+          {props.message}
+        </Text>
+      }
+      <View style={andStyles.modalFooter}>
+        { props.showCancel &&
+          <AndroidButton 
+            title={props.cancel}
+            onPress={() => {
+              dismissDialog();
+              if (props.callback) {
+                props.callback(BUTTON_CANCEL);
+              }
+            }}
+          />
+        }
+        <AndroidButton 
+          title={props.ok}
+          onPress={() => {
+            dismissDialog();
+            if (props.callback) {
+              props.callback(BUTTON_OK);
+            }
+          }}
+        />
+      </View>
+    </View>
+  )
+}
+
+const AndroidButton = ({title, onPress}) => {
+  return (
+    <View style={andStyles.modalButton}>
+      <Pressable
+        style={andStyles.modalPress}
+        android_ripple={ripple}
+        onPress={onPress}>
+        <Text style={andStyles.modalBtnText}>{title}</Text>
+      </Pressable>
+    </View>
+  );
+}
+
+const IOSProgress = (props) => {
+  return (
+    <View style={iosStyle.modalProgress}>
+      <Image
+        style={iosStyle.loadingIcon}
+        source={require('../images/icon/loading.gif')}/>
+      <Text
+        style={iosStyle.proMessage}
+        onPress={() => {
+          dismissLoading();
+        }}>{props.message}</Text>
+    </View>
+  );
+}
+
+const AndroidProgress = (props) => {
+  return (
+    <View style={andStyles.progressDialog}>
+      <View style={andStyles.progressView}>
+        <View style={{
+          width: 48,
+          height: 48
+        }}>
+          <Progress.CircleSnail
+            size={48}
+            duration={667}
+            thickness={4}
+            color={[colorAccent]}
+            direction={'clockwise'}
+            spinDuration={2000}/>
+        </View>
+        <Text
+          style={andStyles.proMessage}
+          onPress={() => {
+            dismissLoading();
+          }}>{props.message}</Text>
+      </View>
+    </View>
+  );
+}
+
+const iosStyle = StyleSheet.create({
+  modalDialog: {
+    width: maxWidth,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    borderRadius: 12,
+    overflow: 'hidden',
+    backgroundColor: 'white'
+  },
+  modalProgress: {
+    width: 180,
+    overflow: 'hidden',
+    alignItems: 'center',
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    borderRadius: 12,
+    backgroundColor: 'white'
+  },
+  loadingIcon: {
+    width: 80,
+    height: 80,
+    transform: [{scale: 1.3}]
+  },
+  title: {
+    paddingTop: 18,
+    paddingLeft: 16,
+    paddingRight: 16,
+    paddingBottom: 16
+  },
+  modalTitle: {
+    color: '#111',
+    fontSize: 18,
+    paddingTop: 18,
+    paddingLeft: 10,
+    paddingRight: 10,
+    fontWeight: 'bold',
+    textAlign: 'center',
+  },
+  message: {
+    color: '#333',
+    fontSize: 14,
+    paddingTop: 4,
+    paddingLeft: 20,
+    paddingRight: 20
+  },
+  proMessage: {
+    color: '#555',
+    fontSize: 15,
+    marginTop: -4,
+    paddingBottom: 14
+  },
+  modalFooter: {
+    width: maxWidth,
+    marginTop: 18,
+    borderTopWidth: 1,
+    borderTopColor: '#EFF1F1',
+    flexDirection: 'row'
+  },
+  btnGroup: {
+    flex: 1,
+    borderRadius: 0,
+    backgroundColor: 'white'
+  },
+  btnRight: {
+    borderLeftWidth: 1,
+    borderLeftColor: '#EFF1F1'
+  },
+  btnView: {
+    flex: 1,
+    height: 45,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  btnText: {
+    color: '#064FE1',
+    fontSize: 14,
+    textAlign: 'center'
+  },
+  btnConfirm: {
+    color: '#064FE1',
+    fontSize: 14,
+    fontWeight: 'bold'
+  }
+})
+
+const andStyles = StyleSheet.create({
+  modalDialog: {
+    width: maxWidth,
+    zIndex: 100,
+    paddingTop: 16,
+    paddingLeft: 20,
+    paddingRight: 20,
+    paddingBottom: 8,
+    borderRadius: 3,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    backgroundColor: 'white'
+  },
+  progressDialog: {
+    width: maxWidth,
+    zIndex: 100,
+    paddingTop: 16,
+    paddingLeft: 24,
+    paddingRight: 24,
+    paddingBottom: 16,
+    borderRadius: 3,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    backgroundColor: 'white'
+  },
+  title: {
+    color: '#000',
+    paddingBottom: 8,
+    fontSize: 18
+  },
+  message: {
+    color: '#333',
+    fontSize: 14,
+    paddingBottom: 8,
+  },
+  proMessage: {
+    flex: 1,
+    color: '#333',
+    fontSize: 15,
+    paddingLeft: 24
+  },
+  modalFooter: {
+    paddingTop: 8,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'flex-end'
+  },
+  modalButton: {
+    marginLeft: 8,
+    borderRadius: 3,
+    overflow: 'hidden'
+  },
+  modalPress: {
+    padding: 8
+  },
+  modalBtnText: {
+    fontSize: 14,
+    color: colorPrimaryDark
+  },
+  progressView: {
+    alignItems: 'center',
+    flexDirection: 'row'
+  }
+});
+
+export default Dialog = {
+  BUTTON_OK: BUTTON_OK,
+  BUTTON_CANCEL: BUTTON_CANCEL,
+  showDialog: showDialog,
+  dismissAll: dismissAll,
+  dismissDialog: dismissDialog,
+  dismissLoading: dismissLoading,
+  showResultDialog: showResultDialog,
+  showProgressDialog: showProgressDialog,
+  modalProps: {
+    avoidKeyboard: true,
+    animationIn: "fadeIn",
+    animationOut: "fadeOut",
+    propagateSwipe: true,
+    useNativeDriver: true,
+    hideModalContentWhileAnimating: true
+  }
+}
+
+//Toast显示位置
+const toastPosition = isIOS ? 0 : -70;
+
+export const InitSomething = () => {
+  global.dialogId = undefined;
+  global.EndView = () => <View style={ui.end}/>
+
+  global.toastShort = (msg) => {
+    if (utils.isNotEmpty(msg)) {
+      if (typeof msg !== 'string')
+        msg = '' + msg;
+      Toast.show(msg, {
+        duration: Toast.durations.SHORT,
+        position: toastPosition,
+        shadow: false,
+        opacity: 0.85,
+        textStyle: {
+          fontSize: 14
+        },
+        backgroundColor: '#222222',
+        containerStyle: {
+          paddingLeft: 16,
+          paddingRight: 16,
+          borderRadius: 50,
+          maxHeight: $vh(80)
+        }
+      });
+    }
+  }
+
+  global.toastLong = (msg) => {
+    if (utils.isNotEmpty(msg)) {
+      if (typeof msg !== 'string')
+        msg = '' + msg;
+      Toast.show(msg, {
+        duration: Toast.durations.LONG,
+        position: toastPosition,
+        shadow: false,
+        opacity: 0.85,
+        textStyle: {
+          fontSize: 14
+        },
+        backgroundColor: '#222222',
+        containerStyle: {
+          paddingLeft: 16,
+          paddingRight: 16,
+          borderRadius: 50,
+          maxHeight: $vh(80)
+        }
+      });
+    }
+  }
+}

+ 202 - 0
Strides-APP/app/components/Dropdown.js

@@ -0,0 +1,202 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { FlatList, Keyboard, Pressable, StyleSheet, Text, View } from 'react-native';
+import Modal from 'react-native-modal';
+import Button from './Button';
+import Dialog, { getDialogWidth } from './Dialog';
+
+//const DialogMaxWidth = $vw(85) > 500 ? 500 : $vw(85);
+//const DialogIOSWidth = $vw(75) > 450 ? 450 : $vw(75);
+
+export default Dropdown = ({
+    list = [],
+    title = '',
+    value,
+    onChange,
+    nameKey,
+    valueKey,
+    extraText='',
+    itemHeight=50,
+    rippleStyle,
+    style = styles.valueView,
+    textStyle = styles.valueText,
+    placeholderStyle=styles.placeText,
+    placeholder='',
+    showIcon = true,
+    iconColor = '#888',
+    iconStyle = styles.iconStyle,
+    autoSelect = true,
+    customerItemView
+  }) => {
+  
+  const refFlat = useRef();
+  const [visible, showDialog] = useState(false);
+  const [selected, changeValue] = useState('');
+  const [currentIndex, setCurrent] = useState(0);
+  useEffect(() => {
+    if (value !== selected) {
+      if (nameKey && valueKey) {
+        for (var i = 0; i < list.length; i++) {
+          let item = list[i];
+          if (item[valueKey] == value) {
+            changeValue(extraText+item[nameKey]);
+            if (list.length > 20) {
+              setCurrent(i > 5 ? i - 4 : 0);
+            }
+            break;
+          }
+        }
+      } else {
+        changeValue(extraText+value);
+      }
+    }
+  }, [value, []]);
+  useEffect(() => {
+    if (autoSelect && list.length > 0) {
+      if (!value) {
+        const item = list[0];
+        /*if (nameKey) {
+          changeValue(item[nameKey]);
+        } else {
+          changeValue(item);
+        }*/
+        if (onChange) {
+          onChange(valueKey ? item[valueKey] : item, 0)
+        }
+      }
+    }
+  }, [list]);
+  const showList = () => {
+    Keyboard.dismiss();
+    showDialog(true);
+    /*if (currentIndex > 0) {
+      console.log(refFlat.current);
+      setTimeout(() => {
+        if (refFlat.current) {
+          refFlat.current.scrollToIndex({
+            animated: false,
+            index: currentIndex,
+            viewPosition: 0.5
+          })
+        }
+      }, 100)
+    }*/
+  }
+  const renderItem = ({ item, index, separators }) => {
+    if (customerItemView) {
+      return customerItemView(item, index, () => {
+        showDialog(false);
+        if (onChange) {
+          onChange(valueKey ? item[valueKey] : item, index)
+        }
+      })
+    } else {
+      return (
+        <Button
+          text={nameKey ? item[nameKey] : item}
+          style={styles.itemView}
+          textStyle={styles.itemText}
+          onClick={() => {
+            showDialog(false);
+            if (onChange) {
+              onChange(valueKey ? item[valueKey] : item, index)
+            }
+          }
+        }/>
+      )
+    }
+  }
+  return (
+    <>
+      <Pressable
+        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>
+        }
+        { showIcon && 
+          <Entypo 
+            name={'chevron-thin-down'}
+            size={14}
+            color={iconColor}
+            style={iconStyle}
+          />
+        }
+      </Pressable>
+      <Modal
+        isVisible={visible}
+        onTouchOutside={() => showDialog(false)}
+        onBackdropPress={() => showDialog(false)}
+        onBackButtonPress={() => showDialog(false)}
+        {...Dialog.modalProps}
+      >
+        <View style={styles.dialog}>
+          { title !== '' && <Text style={styles.title}>{title}</Text> }
+          <FlatList
+            data={list}
+            ref={refFlat}
+            renderItem={renderItem}
+            initialScrollIndex={currentIndex}
+            keyExtractor={(item, index) => index}
+            style={{maxHeight: $vh(55)}}
+            getItemLayout={(data, index) => (
+              {length: itemHeight, offset: itemHeight * index, index}
+            )}
+          />
+        </View>
+      </Modal>
+    </>
+  );
+}
+
+const styles = StyleSheet.create({
+  dialog: {
+    width: getDialogWidth(),
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    paddingTop: isIOS ? 12 : 8,
+    paddingBottom: isIOS ? 12 : 8,
+    backgroundColor: 'white',
+    borderRadius: isIOS ? 10 : 3
+  },
+  title: {
+    color: '#000',
+    paddingTop: 8,
+    paddingLeft: 16,
+    paddingBottom: 16,
+    fontSize: 17,
+    fontWeight: 'bold'
+  },
+  valueView: {
+    paddingLeft: 16,
+    paddingRight: 32,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  valueText: {
+    color: '#000',
+    fontSize: 16
+  },
+  itemView: {
+    borderRadius: 0,
+    backgroundColor: 'white'
+  },
+  itemText: {
+    flex: 1,
+    color: '#333',
+    fontSize: 14,
+    textAlign: 'left',
+    fontWeight: 'normal'
+  },
+  placeText: {
+    flex: 1,
+    color: '#aaa'
+  },
+  iconStyle: {
+    top: '50%',
+    right: 16,
+    marginTop: -7,
+    position: 'absolute',
+  }
+});

+ 145 - 0
Strides-APP/app/components/ModalPortal.js

@@ -0,0 +1,145 @@
+/**
+ * ModalPortal
+ * @邠心vbe on 2022/02/28
+ */
+import React, { Component } from 'react';
+import Modal from 'react-native-modal';
+
+let modal
+export default class ModalPortal extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      showDialog: false,
+      showLoading: false,
+      children: <></>,
+      loadChildren: <></>
+    };
+    modal = this;
+    this.afterHide = undefined;
+  }
+
+  static show(children) {
+    return modal.show(children);
+  }
+
+  static showLoading(children) {
+    return modal.showLoading(children);
+  }
+
+  static dismiss() {
+    modal.dismiss();
+  }
+
+  static dismissLoading() {
+    modal.dismissLoading();
+  }
+
+  static dismissAll() {
+    modal.dismissAll();
+  }
+
+  show(children) {
+    if (isIOS) {
+      this.setState({
+        showDialog: true,
+        children: children,
+      })
+    } else {
+      this.setState({
+        showDialog: true,
+        children: children
+      })
+    }
+  }
+
+  showLoading(children) {
+    if (isIOS) {
+      this.setState({
+        showDialog: true,
+        //showLoading: true,
+        children: children,
+        //loadChildren: children
+      });
+    } else {
+      this.setState({
+        showLoading: true,
+        loadChildren: children
+      });
+    }
+  }
+
+  dismiss() {
+    this.setState({
+      showDialog: false
+    })
+  }
+
+  dismissLoading() {
+    if (isIOS) {
+      this.setState({
+        showDialog: false
+      })
+    } else {
+      this.setState({
+        showLoading: false
+      })
+    }
+  }
+
+  dismissAll() {
+    this.setState({
+      showDialog: false,
+      showLoading: false
+    })
+  }
+
+  onBackPress() {
+    if (this.state.showLoading) {
+      this.dismissLoading();
+    } else if (this.state.showDialog) {
+      this.dismiss();
+    }
+  }
+
+  onModalHide() {
+    console.log('onModalHide', this.afterHide);
+    if (this.afterHide) {
+      console.log('onModalHide');
+      this.afterHide();
+      this.afterHide = undefined;
+    }
+  }
+
+  render() {
+    return (
+      <>
+        <Modal
+          style={{zIndex: 900}}
+          isVisible={this.state.showDialog}
+          avoidKeyboard={true}
+          animationIn={"fadeIn"}
+          animationOut={"fadeOut"}
+          useNativeDriver={true}
+          onBackButtonPress={() => this.onBackPress()}
+          //onModalHide={() => this.onModalHide()}
+        >
+          {this.state.children}
+        </Modal>
+        <Modal
+          style={{zIndex: 900}}
+          isVisible={this.state.showLoading}
+          animationIn="fadeIn"
+          animationOut="fadeOut"
+          useNativeDriver={true}
+          animationInTiming={80}
+          animationOutTiming={100}
+          onBackButtonPress={() => this.onBackPress()}
+          //onModalHide={() => this.onModalHide()}
+        >
+          {this.state.loadChildren}
+        </Modal>
+      </>
+    );
+  }
+}

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

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

+ 74 - 0
Strides-APP/app/components/TextRadius.js

@@ -0,0 +1,74 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+const getRadius = (style) => {
+  let s = undefined;
+  if (Array.isArray(style)) {
+    let res = {}
+    for (let s of style) {
+      res = {...res, ...s};
+    }
+    s = res;
+  } else {
+    s = style
+  }
+  var view = {}, text = {}
+  for (let name in s) {
+    if (name.indexOf('margin') >= 0) {
+      view[name] = s[name];
+      continue;
+    }
+    if (name.indexOf('padding') >= 0) {
+      text[name] = s[name];
+      continue;
+    }
+    if (name.indexOf('color') == 0) {
+      text[name] = s[name];
+      continue;
+    }
+    if (name.indexOf('font') >= 0) {
+      text[name] = s[name];
+      continue;
+    }
+    if (name.indexOf('border') >= 0) {
+      view[name] = s[name];
+      continue;
+    } else if (name.indexOf('Radius') >= 0) {
+      view[name] = s[name];
+      continue;
+    }
+    if (name.indexOf('background') >= 0) {
+      view[name] = s[name];
+      continue;
+    }
+    if (name.indexOf('text') >= 0) {
+      text[name] = s[name];
+      continue;
+    } 
+    if (name.indexOf('position') >= 0) {
+      view[name] = s[name];
+      continue;
+    } else {
+      view[name] = s[name];
+    }
+  }
+  return {
+    view: view,
+    text: text
+  }
+}
+
+const TextRadius = ({style, onPress, children}) => {
+  if (isIOS) {
+    const styles = getRadius(style);
+    return (
+      <View style={styles.view}>
+        <Text style={styles.text} onPress={onPress}>{children}</Text>
+      </View>
+    );
+  } else {
+    return <Text style={style} onPress={onPress}>{children}</Text>;
+  }
+};
+
+export default TextRadius;

+ 79 - 0
Strides-APP/app/components/Toolbar.js

@@ -0,0 +1,79 @@
+import React from 'react';
+import { Image, Pressable, StyleSheet, View } from 'react-native';
+
+export const BackButton = () => {
+  return (
+    <Pressable
+      style={Styles.backIcon}
+      android_ripple = {{
+        color: '#909090',
+        radius: 22,
+        borderless: true
+      }}
+      onPress={() => {
+        goBack();
+      }}>
+      <BackIcon/>
+    </Pressable>
+  )
+}
+
+export const BackIcon = () => {
+  return <MaterialIcons name={isIOS ? 'arrow-back-ios' : 'arrow-back'} size={24} color={'#333333'} />
+}
+
+export const StationBack = ({bottom = 24}) => {
+  return (
+    <Image
+      style={{
+        right: 0,
+        bottom: bottom,
+        width: 125.8,
+        height: 137.64,
+        position: 'absolute'
+      }}
+      source={require('../images/charge/bg-top-station.png')}/>
+  );
+}
+
+export const Styles = StyleSheet.create({
+  toolbar: {
+    height: toolbarSize,
+    paddingLeft: 2,
+    paddingRight: 2,
+    paddingTop: isIOS ? statusHeight : 0,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center',
+    backgroundColor: colorPrimary
+  },
+  backIcon: {
+    width: 48,
+    height: 48,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  content: {
+    flex: 1,
+    paddingRight: 48,
+    alignItems: 'center'
+  },
+  logo: {
+    width:123.8,
+    height: 38.95
+  }
+});
+
+export default Toolbar = (props) => {
+  return (
+    <View style={Styles.toolbar}>
+      <BackButton/>
+      <View style={Styles.content}>
+        <Image
+          source={require('../images/tool-logo.png')}
+          style={Styles.logo}
+        />
+      </View>
+    </View>
+  )
+}

File diff suppressed because it is too large
+ 0 - 0
Strides-APP/app/components/countrys.json


BIN
Strides-APP/app/images/about-logo.png


BIN
Strides-APP/app/images/app-logo.png


BIN
Strides-APP/app/images/charge/bg-top-station.png


BIN
Strides-APP/app/images/charge/charge-complete.png


BIN
Strides-APP/app/images/charge/charge-item-select.png


BIN
Strides-APP/app/images/charge/ic-battery-0.png


BIN
Strides-APP/app/images/charge/ic-battery-1.png


BIN
Strides-APP/app/images/charge/ic-charge-circle.png


BIN
Strides-APP/app/images/charge/ic-payment.png


BIN
Strides-APP/app/images/charge/ic-plus-large.png


BIN
Strides-APP/app/images/charge/ic-plus-middle.png


BIN
Strides-APP/app/images/charge/ic-plus-small.png


BIN
Strides-APP/app/images/charge/ic-type-ac.png


BIN
Strides-APP/app/images/charge/ic-type-chademo.png


BIN
Strides-APP/app/images/charge/ic-type-dc.png


BIN
Strides-APP/app/images/charge/ic-type-rate.png


BIN
Strides-APP/app/images/charge/icon-station-no.png


BIN
Strides-APP/app/images/charge/icon-station.png


BIN
Strides-APP/app/images/charge/icon-type-interfaces.png


BIN
Strides-APP/app/images/charge/icon-type-stops.png


BIN
Strides-APP/app/images/country/AD.png


BIN
Strides-APP/app/images/country/AE.png


BIN
Strides-APP/app/images/country/AF.png


BIN
Strides-APP/app/images/country/AG.png


BIN
Strides-APP/app/images/country/AI.png


BIN
Strides-APP/app/images/country/AL.png


BIN
Strides-APP/app/images/country/AM.png


BIN
Strides-APP/app/images/country/AO.png


BIN
Strides-APP/app/images/country/AR.png


BIN
Strides-APP/app/images/country/AS.png


BIN
Strides-APP/app/images/country/AT.png


BIN
Strides-APP/app/images/country/AU.png


BIN
Strides-APP/app/images/country/AW.png


BIN
Strides-APP/app/images/country/AX.png


Some files were not shown because too many files changed in this diff