Просмотр исходного кода

add components/CheckBoxText.js

wudebin 6 месяцев назад
Родитель
Сommit
726f7ded5b

+ 46 - 0
Strides-SPAPP/app/components/CheckBoxText.js

@@ -0,0 +1,46 @@
+import React from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+import CheckBox from '../components/CheckBox';
+import TextView from './TextView';
+
+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}/>
+    <TextView
+      style={[textStyle, flexText ? {flex: 1} : {}]}
+      onPress={() => {
+        if (onValueChange) onValueChange(!value)
+      }}>{text}</TextView>
+  </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,
+    paddingLeft: 4
+  }
+})

+ 561 - 0
Strides-SPAPP/app/components/CountryIcon.js

@@ -0,0 +1,561 @@
+/**
+ * 国家图标组件
+ * @邠心vbe on 2021/10/08
+ */
+import React from 'react';
+import { Image, StyleSheet, Text } from 'react-native';
+import countries from './countrysLocale.json';
+import Button from './Button';
+import apiUser from '../api/apiUser';
+import { i18nUtil } from '../i18n';
+import TextView from './TextView';
+
+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) {
+        countries.forEach(items => {
+          res.data.forEach(item => {
+            if (item.countryCode == items.countryCode) {
+              const country = {
+                countryNum: item.callingCode,
+                countryCode: item.countryCode,
+                countryName: i18nUtil.analyzeLocaleData(items.countryNames, items.countryName),
+                currency: item.currency,
+                currencySign: item.currencySymbol
+              };
+              list.push(country);
+            }
+          })
+        })
+        if (list.length > 0) {
+          global.ComCountryList = list;
+          if (back) back(global.ComCountryList);
+        }
+      }
+    }).catch(errs => {
+      getLocaleCountryList(back)
+    });
+  } else {
+    if (back) back(global.ComCountryList);
+  }
+}
+
+const getLocaleCountryList = (back) => {
+  const list = []
+  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: i18nUtil.analyzeLocaleData(item.countryNames, 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);
+  }
+}
+
+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}/>
+      <TextView style={styles.countryDropItemText}>{country.countryName} ({'+'+country.countryNum})</TextView>
+      { 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}/>
+      <TextView style={styles.countryDropItemText}>{country.countryName}</TextView>
+      { 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}/>
+      <TextView style={styles.countryDropItemText}>{country.countryName} ({country.currencySign})</TextView>
+      { value == country.countryCode
+      ? <MaterialIcons
+          name='check-circle'
+          color={colorAccent}
+          size={20}/>
+      : <TextView style={styles.countryExchangeRate}>{country.exchangeRate}</TextView>
+      }
+    </Button>
+  )
+}
+
+export const TestCountryFilter = () => {
+  console.log("国家列表");
+  console.log(countries.length, countryNew.length, countryAll.length);
+  return
+  const list = [], lang = {}
+  countries.forEach(item => {
+    for (let i of countryNew) {
+      if (i.countryCode == item.countryCode) {
+        const country = Object.assign({}, i);
+        const names = i.countryNames;
+        /*delete country.countryNames;
+        country.countryNames = {
+          cs: names.ces,//捷克
+          en: names.common,//英语
+          et: names.est,//爱沙尼亚
+          cy: names.cym,//威尔士
+          de: names.deu,//德国
+          fi: names.fin,//芬兰
+          fr: names.fra,//法国
+          hr: names.hrv,//克罗地亚
+          it: names.ita,//意大利
+          ja: names.jpn,//日本
+          ko: names.kor,//韩国
+          km: names.common,//柬埔寨
+          my: names.common,//缅甸
+          nl: names.nld,//荷兰
+          pl: names.pol,//波兰
+          pt: names.por,//葡萄牙
+          ru: names.rus,//俄罗斯
+          sk: names.slk,//斯洛伐克
+          es: names.spa,//西班牙
+          th: langs[country.countryCode],//泰国
+          ur: names.urd,//巴基斯坦-乌尔都语
+          vi: names.common,//越南
+          zh: names.zho,//中文
+        }*/
+        country.countryNames={
+          ...names,
+          hr: names.hr ?? names.en,
+          it: names.it ?? names.en,
+          ja: names.ja ?? names.en
+        }
+        lang[country.countryCode] = names.zho
+        list.push(country);
+        break;
+      }
+    }
+  })
+  console.log('====================================');
+  console.log(list);
+  console.log('====================================');
+  //console.log(lang);
+}
+
+const styles = StyleSheet.create({
+  countryDropItem: {
+    borderRadius: 0,
+    backgroundColor: colorLight
+  },
+  countryDropItemView: {
+    flex: 1,
+    height: 50,
+    paddingLeft: 16,
+    paddingRight: 16,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  countryDropItemText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    paddingLeft: 16
+  },
+  countryExchangeRate: {
+    color: '#999',
+    fontSize: 10,
+  },
+  iconBorder: {
+    borderWidth: 1,
+    borderColor: 'rgba(100, 100, 100, .1)'
+  },
+  payIcon: {
+    width: 50,
+    height: 25
+  }
+})

+ 472 - 0
Strides-SPAPP/app/components/Dialog.js

@@ -0,0 +1,472 @@
+/**
+ * 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';
+import TextView from './TextView';
+
+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';
+
+/**
+ * 显示一个弹窗
+ * @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 || $t('nav.ok'),
+    cancel: props.cancel || $t('nav.cancel'),
+    showCancel: props.showCancel != undefined ? props.showCancel : true,
+    callback: props.callback,
+    onBackPress: props.onBackPress
+  }
+  ModalPortal.show((
+    isIOS ? <IOSDialog {...param}/>
+          : <AndroidDialog {...param}/>
+  ), param?.onBackPress);
+}
+
+/**
+ * 显示一个只有确认按钮的弹窗
+ * @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 || $t('nav.ok'),
+    callback: back
+  }
+  showDialog(param);
+}
+
+const showProgressDialog = (message=$t('nav.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 != '' &&
+        <TextView style={iosStyle.title}>{props.title}</TextView>
+      }
+      { props.message != '' &&
+        <TextView style={[
+          iosStyle.message,
+          {
+            textAlign: props.align
+          }
+        ]}>
+          {props.message}
+        </TextView>
+      }
+      <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 != '' &&
+        <TextView style={andStyles.title}>
+          {props.title}
+        </TextView>
+      }
+      { props.message != '' &&
+        <TextView style={[
+          andStyles.message,
+          {
+            textAlign: props.align
+          }
+        ]}>
+          {props.message}
+        </TextView>
+      }
+      <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}>
+        <TextView style={andStyles.modalBtnText}>{title}</TextView>
+      </Pressable>
+    </View>
+  );
+}
+
+const IOSProgress = (props) => {
+  return (
+    <View style={iosStyle.modalProgress}>
+      <Image
+        style={iosStyle.loadingIcon}
+        source={require('../images/icon/loading.gif')}/>
+      <TextView
+        style={iosStyle.proMessage}
+        onPress={() => {
+          dismissLoading();
+        }}>{props.message}</TextView>
+    </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>
+        <TextView
+          style={andStyles.proMessage}
+          onPress={() => {
+            dismissLoading();
+          }}>{props.message}</TextView>
+      </View>
+    </View>
+  );
+}
+
+const iosStyle = StyleSheet.create({
+  modalDialog: {
+    width: maxWidth,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    borderRadius: 12,
+    overflow: 'hidden',
+    backgroundColor: colorLight
+  },
+  modalProgress: {
+    width: 180,
+    overflow: 'hidden',
+    alignItems: 'center',
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    borderRadius: 12,
+    backgroundColor: colorLight
+  },
+  loadingIcon: {
+    width: 80,
+    height: 80,
+    transform: [{scale: 1.3}]
+  },
+  title: {
+    color: '#111',
+    fontSize: 18,
+    paddingTop: 18,
+    paddingLeft: 10,
+    paddingRight: 10,
+    fontWeight: 'bold',
+    textAlign: 'center',
+  },
+  message: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 8,
+    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: colorLight
+  },
+  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: 4,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    backgroundColor: colorLight
+  },
+  progressDialog: {
+    width: maxWidth,
+    zIndex: 100,
+    paddingTop: 16,
+    paddingLeft: 24,
+    paddingRight: 24,
+    paddingBottom: 16,
+    borderRadius: 4,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    backgroundColor: colorLight
+  },
+  title: {
+    color: '#000',
+    paddingBottom: 8,
+    fontSize: 18
+  },
+  message: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingBottom: 8,
+  },
+  proMessage: {
+    flex: 1,
+    color: textPrimary,
+    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: colorPrimary
+  },
+  progressView: {
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  endView: {
+    width: 16,
+    paddingTop: 16
+  },
+  halfView: {
+    width: 8,
+    paddingTop: 8
+  }
+});
+
+export default Dialog = {
+  dialogWidth: maxWidth,
+  BUTTON_OK: BUTTON_OK,
+  BUTTON_CANCEL: BUTTON_CANCEL,
+  isShowing: ModalPortal.isShowing,
+  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
+  },
+  styles: isIOS ? iosStyle : andStyles,
+  IOSProgress: IOSProgress,
+  AndroidButton: AndroidButton
+}
+
+//Toast显示位置
+const toastPosition = isIOS ? 0 : -70;
+
+const getStringMessage = (msg) => {
+  if (typeof msg == 'object') {
+    if (msg.err) {
+      return "" + msg.err;
+    } else if (msg.msg) {
+      return "" + msg.msg;
+    } else if (msg.message) {
+      return "" + msg.message;
+    } else {
+      return JSON.stringify(msg);
+    }
+  } else {
+    return "" + msg;
+  }
+}
+
+export const InitSomething = () => {
+  global.dialogId = undefined;
+  global.EndView = ({half=false}) => (
+    half
+    ? <View style={andStyles.halfView}/>
+    : <View style={andStyles.endView}/>
+  )
+
+  global.toastShort = (msg) => {
+    if (typeof msg !== 'string')
+      msg = getStringMessage(msg);
+    if (utils.isNotEmpty(msg)) {
+      Toast.show(msg, {
+        duration: Toast.durations.SHORT,
+        position: toastPosition,
+        shadow: false,
+        opacity: 0.85,
+        textStyle: {
+          fontSize: 14,
+          lineHeight: 18
+        },
+        backgroundColor: '#222222',
+        containerStyle: {
+          paddingLeft: 16,
+          paddingRight: 16,
+          borderRadius: 50,
+          maxHeight: $vh(80)
+        }
+      });
+    }
+  }
+
+  global.toastLong = (msg) => {
+    if (typeof msg !== 'string')
+      msg = getStringMessage(msg);
+    if (utils.isNotEmpty(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)
+        }
+      });
+    }
+  }
+}

+ 250 - 0
Strides-SPAPP/app/components/Dropdown.js

@@ -0,0 +1,250 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { FlatList, Keyboard, Pressable, StyleSheet, Modal, View, Text } from 'react-native';
+import Button from './Button';
+import Dialog from './Dialog';
+import TextView from './TextView';
+import app from '../../app.json';
+import MyModal from './MyModal';
+
+//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,
+    prefixText='',//前缀
+    suffixText='',//后缀
+    itemHeight=50,
+    prefixList='',//列表前缀
+    suffixList='',//列表后缀
+    rippleStyle=ripple,
+    style = styles.valueView,
+    textStyle = styles.valueText,
+    placeholderStyle=styles.placeText,
+    placeholder='',
+    showText = true,
+    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) {
+      changeItem();
+    }
+  }, [value, []]);
+
+  useEffect(() => {
+    if (autoSelect && list.length > 0) {
+      if (value == undefined) {
+        const item = list[0];
+        if (nameKey) {
+          changeValue(prefixText+item[nameKey]+suffixText);
+        } else {
+          changeValue(item);
+        }
+        setChange(valueKey ? item[valueKey] : item, 0);
+      } else {
+        changeItem(true);
+      }
+    }
+  }, [list]);
+
+  const changeItem = (init) => {
+    if (nameKey && valueKey) {
+      for (var i = 0; i < list.length; i++) {
+        let item = list[i];
+        if (item[valueKey] == value) {
+          changeValue(prefixText+item[nameKey]+suffixText);
+          if (list.length > 20) {
+            setCurrent(i > 5 ? i - 4 : 0);
+          }
+          return;
+        }
+      }
+      if (init) {
+        const item = list[0];
+        setChange(item[valueKey], 0);
+      }
+    } else {
+      if (init) {
+        let _i = list.indexOf(value);
+        if (_i >= 0) {
+          setChange(list[_i], _i);
+        } else {
+          setChange(list[0], 0);
+        }
+      } else {
+        changeValue(prefixText+value+suffixText);
+      }
+    }
+  }
+  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 }) => {
+    const _value = (valueKey ? item[valueKey] : item);
+    if (customerItemView) {
+      return customerItemView(item, index, () => {
+        showDialog(false);
+        setChange(_value, index);
+      })
+    } else {
+      return (
+        <Button
+          text={prefixList + (nameKey ? item[nameKey] : item) + suffixList}
+          style={styles.itemView}
+          textStyle={styles.itemText}
+          onClick={() => {
+            showDialog(false);
+            setChange(_value, index);
+          }}
+          iconRight={(_value == value) && <MaterialIcons name="radio-button-checked" color={colorAccent} size={22}/>}
+        />
+      )
+    }
+  }
+  const setChange = (v, i) => {
+    setTimeout(() => {
+      if (onChange) {
+        onChange(v, i)
+      }
+    }, 300);
+    
+  }
+  return (
+    <>
+      <Pressable
+        style={({pressed }) => [
+          pressed && isIOS && {
+            backgroundColor: rippleColor
+          },
+          style
+        ]}
+        android_ripple={rippleStyle}
+        onPress={() => showList()}>
+        { showText && 
+          ( selected 
+            ? <TextView style={[textStyle, styles.textView]} numberOfLines={1}>{selected}</TextView>
+            : <TextView style={[placeholderStyle, styles.textView]} numberOfLines={1}>{placeholder}</TextView>
+          )
+        }
+        { showIcon && (isIOS || app.isLumiWhitelabel
+        ? <MaterialIcons 
+            name={'keyboard-arrow-down'}
+            size={24}
+            color={iconColor}
+            style={iconStyle}
+          />
+        : <MaterialIcons
+            name={'arrow-drop-down'}
+            size={24}
+            color={iconColor}
+            style={iconStyle}
+          />)
+        }
+      </Pressable>
+      <MyModal
+        visible={visible}
+        style={styles.dialogContent}
+        onLayerPress={() => showDialog(false)}>
+        <View style={$padding(4, 0, 0)}>
+          { title !== '' && <TextView style={styles.title}>{title}</TextView> }
+          <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>
+      </MyModal>
+    </>
+  );
+}
+
+const styles = StyleSheet.create({
+  dialog: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: 'rgba(0,0,0,.6)'
+  },
+  dialogContent: {
+    width: Dialog.dialogWidth,
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    paddingTop: isIOS ? 12 : 8,
+    paddingBottom: isIOS ? 12 : 8,
+    backgroundColor: colorLight,
+    borderRadius: isIOS ? 10 : 4
+  },
+  title: {
+    color: '#000',
+    paddingTop: 8,
+    paddingLeft: 16,
+    paddingBottom: 16,
+    fontSize: 17,
+    fontWeight: 'bold'
+  },
+  valueView: {
+    paddingLeft: 16,
+    paddingRight: 8,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  valueText: {
+    color: '#000',
+    fontSize: 16
+  },
+  itemView: {
+    borderRadius: 0,
+    backgroundColor: colorLight
+  },
+  itemText: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 14,
+    textAlign: 'left',
+    fontWeight: 'normal'
+  },
+  placeText: {
+    flex: 1,
+    color: textPlacehoder
+  },
+  iconStyle: {
+    marginLeft: 8
+  },
+  textView: {
+    flex: 1,
+    flexDirection: 'column'
+  }
+});

+ 37 - 0
Strides-SPAPP/app/components/HeaderTitle.js

@@ -0,0 +1,37 @@
+import React from 'react';
+import { Platform, StyleSheet, Text } from 'react-native';
+
+const HeaderTitle = ({scope, title="", style=styles.titleColor}) => (
+  <Text
+    ariaLevel="1"
+    numberOfLines={1}
+    allowFontScaling={false}
+    accessibilityRole="header"
+    style={[styles.title, style]}>
+    {scope ? $t(scope) : title}
+  </Text>
+);
+
+export default HeaderTitle;
+
+const styles = StyleSheet.create({
+  title: Platform.select({
+    ios: {
+      fontSize: 17,
+      fontWeight: '600'
+    },
+    android: {
+      fontSize: 20,
+      //fontFamily: 'sans-serif-medium',
+      fontWeight: 'normal',
+      lineHeight: 26
+    },
+    default: {
+      fontSize: 18,
+      fontWeight: '500'
+    }
+  }),
+  titleColor: {
+    color: pageTitleTint
+  }
+})