vbea пре 2 година
родитељ
комит
4e30fe72b1
36 измењених фајлова са 1393 додато и 385 уклоњено
  1. 20 0
      Strides-APP/app/api/apiArticle.js
  2. 8 1
      Strides-APP/app/api/apiNotification.js
  3. 13 3
      Strides-APP/app/api/apiUpload.js
  4. 1 1
      Strides-APP/app/api/http.js
  5. 5 0
      Strides-APP/app/components/Button.js
  6. 11 5
      Strides-APP/app/components/TextView.js
  7. 7 1
      Strides-APP/app/i18n/locales/en.js
  8. 12 6
      Strides-APP/app/i18n/locales/zh-TW.js
  9. 9 3
      Strides-APP/app/i18n/locales/zh.js
  10. 12 6
      Strides-APP/app/pages/Router.js
  11. 14 0
      Strides-APP/app/pages/alert/AlertUtil.js
  12. 0 120
      Strides-APP/app/pages/alert/Campaign.js
  13. 24 20
      Strides-APP/app/pages/alert/ItemAlert.js
  14. 127 0
      Strides-APP/app/pages/alert/ItemArticle.js
  15. 159 0
      Strides-APP/app/pages/alert/ItemCampaign.js
  16. 35 7
      Strides-APP/app/pages/alert/ListAlerts.js
  17. 124 0
      Strides-APP/app/pages/alert/ListCampaign.js
  18. 125 0
      Strides-APP/app/pages/alert/ListNews.js
  19. 76 9
      Strides-APP/app/pages/alert/Notification.js
  20. 0 135
      Strides-APP/app/pages/alert/Promotions.js
  21. 29 10
      Strides-APP/app/pages/alert/ViewAlerts.js
  22. 206 0
      Strides-APP/app/pages/alert/ViewArticle.js
  23. 250 0
      Strides-APP/app/pages/alert/ViewCampaign.js
  24. 3 3
      Strides-APP/app/pages/home/Drawer.js
  25. 23 6
      Strides-APP/app/pages/member/MembersList.js
  26. 1 2
      Strides-APP/app/pages/my/EditProfile.js
  27. 4 3
      Strides-APP/app/pages/my/Feedback.js
  28. 65 25
      Strides-APP/app/pages/my/Profile.js
  29. 2 2
      Strides-APP/app/pages/my/ProfileV2.js
  30. 8 5
      Strides-APP/app/pages/sign/Register.js
  31. 2 3
      Strides-APP/app/pages/sign/RegisterDriver.js
  32. 4 4
      Strides-APP/app/pages/sign/RegisterV2.js
  33. 3 3
      Strides-APP/app/pages/sign/RegisterV3.js
  34. 2 2
      Strides-APP/app/pages/sign/RegisterV4.js
  35. 8 0
      Strides-APP/app/utils/utils.js
  36. 1 0
      Strides-APP/package.json

+ 20 - 0
Strides-APP/app/api/apiArticle.js

@@ -0,0 +1,20 @@
+import { del, get, post } from "./http";
+
+const prefix = 'devicesApi/notification/';
+
+export default {
+  /**
+   * 查询新闻列表
+   * @param {String} articleId 最后一个列表对象的Id(用于分页)
+   * @returns Promise
+   */
+  getArticleList(articleId) {
+    return get(prefix + "articles", {articleId})
+  },
+  readMessage(articleId) {
+    return get(prefix + "read-article", {articleId})
+  },
+  getCampaignList(articleId) {
+    return get("devicesApi/notification/campaigns", {articleId})
+  }
+}

+ 8 - 1
Strides-APP/app/api/apiNotification.js

@@ -9,7 +9,7 @@ export default {
   /**
    * 查询通知列表
    * @param {String} notificationId 最后一个列表对象的Id(用于分页)
-   * @returns 
+   * @returns Promise
    */
   getNotificationList(notificationId) {
     return get(prefix + "alerts", {notificationId})
@@ -19,5 +19,12 @@ export default {
   },
   deleteMessage(notificationId) {
     return del(prefix + "alerts/" + notificationId)
+  },
+  /**
+   * 获取各类通知的数量统计
+   * @returns Promise
+   */
+  getTabDotCount() {
+    return get(prefix + "tab-count")
   }
 }

+ 13 - 3
Strides-APP/app/api/apiUpload.js

@@ -1,10 +1,11 @@
 import { get, upload } from "./http";
 
-const prefix = 'devicesApi/picture/upload/';
+const prefix = 'devicesApi/picture/upload';
+const prefixObs = 'devicesApi/picture/obs-upload';
 
 export default {
   /**
-   * 上传图片
+   * 上传图片(已过时)
    * @param {*} uri 图片地址
    * @param {*} mime 图片类型
    * @param {*} type 图片分类 ↓
@@ -15,7 +16,7 @@ export default {
    * PDVL 驾驶证
    * @returns 服务器路径
    */
-  uploadImage(uri, mime, type) {
+  uploadImageOld(uri, mime, type) {
     let data = new FormData();
     data.append('file', {
       uri: uri,
@@ -24,6 +25,15 @@ export default {
     });
     return upload(prefix, data, {photoSubDir: type});
   },
+  uploadImage(uri, mime, type="") {
+    let data = new FormData();
+    data.append('file', {
+      uri: uri,
+      type: mime,
+      name: getFileName(uri)
+    });
+    return upload(prefixObs, data, {photoSubDir: type});
+  },
   testNotification() {
     return get('devicesApi/googleNotification/doNotification');
   }

+ 1 - 1
Strides-APP/app/api/http.js

@@ -80,7 +80,7 @@ export const post = (path, params) => {
   });
 }
 
-export const upload = (path, params, header) => {
+export const upload = (path, params, header={}) => {
   return new Promise((resolve, reject) => {
     Axios.post(host + service + path, params, {
       method: 'POST',

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

@@ -147,6 +147,11 @@ const getElevation = (style, d, e, r) => {
   return s;
 }
 
+/**
+ * Shadow阴影生成
+ * @param {*} e 阴影大小
+ * @returns 样式json
+ */
 export const ElevationObject = (e) => {
   if (e) {
     return {

+ 11 - 5
Strides-APP/app/components/TextView.js

@@ -50,6 +50,10 @@ const getRadius = (style, fixedAlign) => {
       view[name] = s[name];
       continue;
     }
+    if (name.indexOf('line') >= 0) {
+      text[name] = s[name];
+      continue;
+    }
     view[name] = s[name];
   }
   if (fixedAlign) {
@@ -58,11 +62,13 @@ const getRadius = (style, fixedAlign) => {
       view.alignItems = "center";
       view.flexDirection = "row";
     }
-    if (text.fontSize) {
-      text.lineHeight = text.fontSize * 1.3;
-    } else {
-      text.fontSize = 14;
-      text.lineHeight = 18.2;
+    if (!text.lineHeight) {
+      if (text.fontSize) {
+        text.lineHeight = text.fontSize * 1.3;
+      } else {
+        text.fontSize = 14;
+        text.lineHeight = 18.2;
+      }
     }
     text.includeFontPadding = false;
   }

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

@@ -478,10 +478,16 @@ export default {
   },
   notification: {
     tabAlerts: "Alerts",
+    tabCampaign: "Campaign",
     tabPromotions: "News",
     viewMessage: "View Message",
     deleteMessage: "Delete",
     confirmDelete: "Are you sure you want to delete this message?",
-    empty: "Empty"
+    empty: "Empty",
+    labelSummary: "SUMMARY:",
+    labelDetails: "DETAILS:",
+    labelLinks: "LINKS:",
+    startTime: "Start Time: ",
+    endTime: "End Time: "
   }
 }

+ 12 - 6
Strides-APP/app/i18n/locales/zh-TW.js

@@ -477,11 +477,17 @@ export default {
     btnWhatsapp: "WhatsApp"
   },
   notification: {
-    tabAlerts: "資訊",
-    tabPromotions: "活動",
-    viewMessage: "消息資訊",
-    deleteMessage: "刪除資訊",
-    confirmDelete: "您確認要刪除這條資訊嗎?",
-    empty: "空空如也"
+    tabAlerts: "訊息",
+    tabCampaign: "活動",
+    tabPromotions: "新聞",
+    viewMessage: "訊息",
+    deleteMessage: "刪除訊息",
+    confirmDelete: "您確認要刪除這條訊息嗎?",
+    empty: "空空如也",
+    labelSummary: "概要:",
+    labelDetails: "詳情:",
+    labelLinks: "鏈接:",
+    startTime: "開始時間:",
+    endTime: "結束時間:"
   }
 }

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

@@ -478,10 +478,16 @@ export default {
   },
   notification: {
     tabAlerts: "通知",
-    tabPromotions: "活动",
-    viewMessage: "消息通知",
+    tabCampaign: "活动",
+    tabPromotions: "新闻",
+    viewMessage: "信息",
     deleteMessage: "删除通知",
     confirmDelete: "您确定要删除此通知吗?",
-    empty: "空空如也"
+    empty: "空空如也",
+    labelSummary: "摘要:",
+    labelDetails: "详情:",
+    labelLinks: "链接:",
+    startTime: "开始时间:",
+    endTime: "结束时间:"
   }
 }

+ 12 - 6
Strides-APP/app/pages/Router.js

@@ -57,9 +57,10 @@ import MembersList from './member/MembersList';
 import ApplyMember from './member/ApplyMember';
 import Contact from './about/Contact';
 import Notification from './alert/Notification';
-import Message from './alert/Message';
-import Campaign from './alert/Campaign';
+import ViewAlerts from './alert/ViewAlerts';
+import ViewCampaign from './alert/ViewCampaign';
 import RefundPolicy from './payment/RefundPolicy';
+import ViewArticle from './alert/ViewArticle';
 
 export var PageList = {
   'splash': {
@@ -263,12 +264,17 @@ export var PageList = {
   'viewMessage': {
     title: 'View Message',
     titleScope: 'notification.viewMessage',
-    component: Message
+    component: ViewAlerts
   },
-  'viewPromotion': {
-    title: 'View Message',
+  'viewArticle': {
+    title: 'View Article',
+    titleScope: 'notification.viewMessage',
+    component: ViewArticle
+  },
+  'viewCampaign': {
+    title: 'View Campaign',
     titleScope: 'notification.viewMessage',
-    component: Campaign
+    component: ViewCampaign
   },
   'notify': {
     title: 'Notification Test',

+ 14 - 0
Strides-APP/app/pages/alert/AlertUtil.js

@@ -0,0 +1,14 @@
+export default {
+  setBadgeCount(info={}) {
+    global.alertBadgeCountInfo = info;
+  },
+  getAlertCount() {
+    return global.alertBadgeCountInfo?.alertCount || 0
+  },
+  getNewsCount() {
+    return global.alertBadgeCountInfo?.newsCount || 0
+  },
+  getCampaignCount() {
+    return global.alertBadgeCountInfo?.campaignCount || 0
+  }
+}

+ 0 - 120
Strides-APP/app/pages/alert/Campaign.js

@@ -1,120 +0,0 @@
-/**
- * 活动信息详情
- * @邠心vbe on 2023/08/17
- */
-import React, { Component } from 'react';
-import { View, Text, StyleSheet, ScrollView, Image } from 'react-native';
-import apiNotification from '../../api/apiNotification';
-import Button from '../../components/Button';
-import Dialog from '../../components/Dialog';
-import TextView from '../../components/TextView';
-import { PageList } from '../Router';
-
-export default class Campaign extends Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      id: "",
-      messageInfo: {
-        createTime: "",
-        notificationText: "",
-        notificationTitle: "",
-        notificationType: ""
-      }
-    };
-  }
-  
-  componentDidMount() {
-    if (this.props.route?.params?.id) {
-      this.setState({
-        id: this.props.route?.params?.id
-      }, () => {
-        this.readMessage();
-      })
-    }
-  }
-
-  readMessage() {
-    Dialog.showProgressDialog();
-    apiNotification.readMessage(this.state.id).then(res => {
-      if (res.data) {
-        this.setState({
-          messageInfo: res.data
-        });
-      }
-    }).catch(err => {
-      toastShort(err);
-    }).finally(() => {
-      Dialog.dismissLoading();
-    })
-  }
-
-  submitFeedback() {
-    startPage(PageList.feedback);
-  }
-
-  render() {
-    return (
-      <View style={styles.container}>
-        <ScrollView
-          style={ui.flex1}
-          contentContainerStyle={styles.content}
-          stickyHeaderIndices={[1]}>
-          <Image
-            style={styles.topImage}
-            resizeMode="cover"
-          />
-          <View style={styles.header}>
-            <TextView
-              style={styles.textTitle}>
-              {this.state.messageInfo.notificationTitle}
-            </TextView>
-            <TextView
-              style={styles.textDate}
-              numberOfLines={1}>
-              {this.state.messageInfo.createTime}
-            </TextView>
-          </View>
-          <TextView
-            style={styles.textMessage}>
-            {this.state.messageInfo.notificationText}
-          </TextView>
-        </ScrollView>
-      </View>
-    );
-  }
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    backgroundColor: pageBackground
-  },
-  textTitle: {
-    color: textPrimary,
-    fontSize: 18,
-    fontWeight: 'bold'
-  },
-  textDate: {
-    color: textSecondary,
-    fontSize: 10,
-    paddingTop: 1
-  },
-  topImage: {
-    width: $vw(100),
-    height: $vw(80),
-    backgroundColor: "#F0F0F0"
-  },
-  header: {
-    padding: 16,
-    backgroundColor: pageBackground
-  },
-  content: {
-    backgroundColor: pageBackground
-  },
-  textMessage: {
-    color: textPrimary,
-    fontSize: 14,
-    padding: 16,
-  }
-})

+ 24 - 20
Strides-APP/app/pages/alert/ItemView.js → Strides-APP/app/pages/alert/ItemAlert.js

@@ -3,7 +3,7 @@
  * @邠心vbe on 2023/08/17
  */
 import React from 'react';
-import { Pressable, StyleSheet, Text, View } from 'react-native';
+import { Pressable, StyleSheet, View } from 'react-native';
 import TextView from '../../components/TextView';
 
 const IconType = ({type, style, size=32, color=colorAccent}) => {
@@ -83,7 +83,7 @@ const IconType = ({type, style, size=32, color=colorAccent}) => {
   }
 }
 
-export default ItemView = ({item, index, separators, onPress, onLongPress}) => {
+export default ItemAlert = ({item, index, separators, onPress, onLongPress}) => {
   const getDotColor = () => {
     if (item.readStatus) {
       if (item?.notificationTitle.indexOf("Low") >= 0/* || item.notificationType == "Announcement"*/) {
@@ -107,19 +107,21 @@ export default ItemView = ({item, index, separators, onPress, onLongPress}) => {
         color={getDotColor()}
       />
       <View style={styles.itemContent}>
-        <View style={ui.flexc}>
-          <TextView
-            style={[styles.textTitle, (!item.readStatus && styles.unread)]}
-            numberOfLines={1}
-            ellipsizeMode="tail">
-            {item.notificationTitle}
-          </TextView>
-          <TextView
-            style={[styles.textDate, (!item.readStatus && styles.unread)]}
-            numberOfLines={1}>
-            {item.createTime}
-          </TextView>
-        </View>
+        <TextView
+          style={styles.textTitle}
+          numberOfLines={1}
+          ellipsizeMode="tail">
+          {item.notificationTitle}
+        </TextView>
+        <TextView
+          style={styles.textDate}
+          numberOfLines={1}>
+          <MaterialCommunityIcons
+            name="clock-time-four-outline"
+            size={10}
+            style={styles.textDate}/>
+          {" " + item.createTime}
+        </TextView>
         <TextView
           style={styles.textMessage}
           numberOfLines={2}>
@@ -133,12 +135,10 @@ export default ItemView = ({item, index, separators, onPress, onLongPress}) => {
 const styles = StyleSheet.create({
   notyItemView: {
     padding: 16,
-    alignItems: 'center',
     flexDirection: 'row'
   },
   unotyItemView: {
     padding: 16,
-    alignItems: 'center',
     flexDirection: 'row',
     backgroundColor: "#F0F0F0"
   },
@@ -158,11 +158,15 @@ const styles = StyleSheet.create({
   textTitle: {
     flex: 1,
     color: textPrimary,
-    fontSize: 15
+    fontSize: 15,
+    fontWeight: 'bold',
+    paddingBottom: 4
   },
   textDate: {
-    color: textPrimary,
-    fontSize: 12
+    color: textSecondary,
+    fontSize: 10,
+    lineHeight: 10,
+    paddingBottom: 4
   },
   unread: {
     fontWeight: 'bold'

+ 127 - 0
Strides-APP/app/pages/alert/ItemArticle.js

@@ -0,0 +1,127 @@
+/**
+ * 文章渲染项目组件
+ * @邠心vbe on 2023/10/24
+ */
+import React from 'react';
+import { Image, Pressable, StyleSheet, View } from 'react-native';
+import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
+
+export default ItemArticle = ({item, index, separators, onPress}) => {
+  return (
+    <Pressable
+      style={item.readStatus ? styles.notyItemView : styles.unotyItemView}
+      onPress={onPress}
+      android_ripple={ripple}>
+      <View style={ui.center}>
+        <Image
+          source={{uri: utils.getImageUrl(item.articleImages[0].articleImagePath)}}
+          resizeMode="cover"
+          style={styles.articleImage}/>
+        <View style={ui.flexc}>
+          <MaterialCommunityIcons
+            name="eye-check-outline"
+            size={13}
+            color={textPrimary}/>
+          <TextView
+            style={styles.textView}
+            numberOfLines={1}>
+            {item.articleViews}
+          </TextView>
+        </View>
+      </View>
+      <View style={styles.itemContent}>
+        <TextView
+          style={styles.textTitle}
+          numberOfLines={1}
+          ellipsizeMode="tail">
+          {item.articleTitle}
+        </TextView>
+        <TextView
+          style={styles.textDate}
+          numberOfLines={1}>
+          <MaterialCommunityIcons
+            name="clock-time-four-outline"
+            size={10}
+            style={styles.textDate}/>
+          {" " + item.createTime}
+        </TextView>
+        <View style={ui.flex}>
+          <TextView 
+            style={styles.labelTypeText}
+            numberOfLines={1}>
+            {item.articleTypeName}
+          </TextView>
+        </View>
+        <TextView
+          style={styles.textMessage}
+          numberOfLines={4}>
+          {item.articleContent}
+        </TextView>
+      </View>
+    </Pressable>
+  )
+}
+
+const styles = StyleSheet.create({
+  notyItemView: {
+    padding: 16,
+    flexDirection: 'row'
+  },
+  unotyItemView: {
+    padding: 16,
+    flexDirection: 'row',
+    backgroundColor: "#F0F0F0"
+  },
+  articleImage: {
+    width: 85,
+    height: 85,
+    borderRadius: 6,
+    marginRight: 16,
+    marginBottom: 8
+  },
+  readIcon: {
+    top: 2,
+    left: -5,
+    width: 7,
+    height: 6,
+    position: 'absolute'
+  },
+  itemContent: {
+    flex: 1
+  },
+  textTitle: {
+    color: textPrimary,
+    fontSize: 15,
+    fontWeight: 'bold',
+    paddingBottom: 4
+  },
+  textDate: {
+    color: textSecondary,
+    fontSize: 10,
+    lineHeight: 10,
+    paddingBottom: 4
+  },
+  textView: {
+    color: textPrimary,
+    fontSize: 11,
+    lineHeight: 12,
+    paddingLeft: 4,
+    paddingRight: 16
+  },
+  labelTypeText: {
+    fontSize: 10,
+    borderWidth: 1,
+    borderRadius: 4,
+    borderColor: colorPrimary,
+    ...$padding(2, 6)
+  },
+  unread: {
+    fontWeight: 'bold'
+  },
+  textMessage: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 2
+  }
+})

+ 159 - 0
Strides-APP/app/pages/alert/ItemCampaign.js

@@ -0,0 +1,159 @@
+/**
+ * 活动渲染项目组件
+ * @邠心vbe on 2023/10/25
+ */
+import React from 'react';
+import { Image, Pressable, StyleSheet, View } from 'react-native';
+import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
+
+const getColorByType = (type) => {
+  switch (type) {
+    case "Ended":
+      return {
+        backgroundColor: "#E11919"
+      }
+    case "Upcoming":
+      return {
+        backgroundColor: "#FFAA2C"
+      }
+  }
+}
+
+export default ItemCampaign = ({item, index, separators, onPress}) => {
+  return (
+    <Pressable
+      style={item.readStatus ? styles.notyItemView : styles.unotyItemView}
+      onPress={onPress}
+      android_ripple={ripple}>
+      <View style={ui.center}>
+        <Image
+          source={{uri: utils.getImageUrl(item.articleImages[0].articleImagePath)}}
+          resizeMode="cover"
+          style={styles.articleImage}/>
+        <View style={ui.flexc}>
+          <MaterialCommunityIcons
+            name="eye-check-outline"
+            size={13}
+            color={textPrimary}/>
+          <TextView
+            style={styles.textView}
+            numberOfLines={1}>
+            {item.articleViews}
+          </TextView>
+        </View>
+      </View>
+      <View style={styles.itemContent}>
+        <View>
+          <TextView 
+            style={[styles.labelTypeText, getColorByType(item.campaignMark)]}
+            numberOfLines={1}>
+            {item.campaignMark}
+          </TextView>
+          <TextView
+            style={styles.textTitle}
+            numberOfLines={2}
+            ellipsizeMode="tail">
+            <TextView
+              style={styles.hideText}
+              numberOfLines={1}>
+              {item.campaignMark}
+            </TextView>
+            {item.articleTitle}
+          </TextView>
+        </View>
+        <TextView
+          style={styles.textDate}
+          numberOfLines={1}>
+          <MaterialCommunityIcons
+            name="calendar-clock"
+            size={12}/>
+          {" " + item.campaignDuration}
+        </TextView>
+        <TextView
+          style={styles.textMessage}
+          numberOfLines={4}>
+          {item.articleContent}
+        </TextView>
+      </View>
+    </Pressable>
+  )
+}
+
+const styles = StyleSheet.create({
+  notyItemView: {
+    padding: 16,
+    flexDirection: 'row'
+  },
+  unotyItemView: {
+    padding: 16,
+    flexDirection: 'row',
+    backgroundColor: "#F0F0F0"
+  },
+  articleImage: {
+    width: 85,
+    height: 85,
+    borderRadius: 6,
+    marginRight: 16,
+    marginBottom: 8
+  },
+  readIcon: {
+    top: 2,
+    left: -5,
+    width: 7,
+    height: 6,
+    position: 'absolute'
+  },
+  itemContent: {
+    flex: 1
+  },
+  textTitle: {
+    color: textPrimary,
+    fontSize: 15,
+    lineHeight: 20,
+    fontWeight: 'bold',
+    paddingBottom: 4,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  labelTypeText: {
+    color: textLight,
+    height: 16,
+    fontSize: 10,
+    borderRadius: 4,
+    marginTop: 2,
+    ...$padding(0, 6),
+    position: 'absolute',
+    backgroundColor: "#1ABD00"
+  },
+  hideText: {
+    height: 16,
+    opacity: 0,
+    fontSize: 10,
+    ...$padding(2, 8),
+  },
+  textDate: {
+    color: textSecondary,
+    fontSize: 11,
+    lineHeight: 12,
+    fontWeight: 'bold',
+    paddingBottom: 4,
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  textView: {
+    color: textPrimary,
+    fontSize: 11,
+    lineHeight: 12,
+    paddingLeft: 4,
+    paddingRight: 16
+  },
+  unread: {
+    fontWeight: 'bold'
+  },
+  textMessage: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 2
+  }
+})

+ 35 - 7
Strides-APP/app/pages/alert/Alerts.js → Strides-APP/app/pages/alert/ListAlerts.js

@@ -8,9 +8,10 @@ import apiNotification from '../../api/apiNotification';
 import Dialog from '../../components/Dialog';
 import { MyRefreshProps } from '../../components/ThemesConfig';
 import { PageList } from '../Router';
-import ItemView from './ItemView';
+import AlertUtil from './AlertUtil';
+import ItemView from './ItemAlert';
 
-export default class Alerts extends Component {
+export default class ListAlerts extends Component {
   constructor(props) {
     super(props);
     this.state = {
@@ -62,11 +63,15 @@ export default class Alerts extends Component {
     })
   }
 
-  getMessageList() {
+  onRefresh() {
     this.setState({
       refreshing: true
     })
-    apiNotification.getNotificationList().then(res => {
+    this.getMessageList();
+  }
+
+  getMessageList() {
+    apiNotification.getNotificationList("").then(res => {
       if (res.data) {
         this.setState({
           dataList: res.data
@@ -81,6 +86,28 @@ export default class Alerts extends Component {
     })
   }
 
+  getMessageListPage() {
+    const count = AlertUtil.getAlertCount();
+    if (this.state.dataList.length > 0 && this.state.dataList.length < count) {
+      const last = this.state.dataList[this.state.dataList.length-1]
+      if (last.notificationId) {
+        apiNotification.getNotificationList(last.notificationId).then(res => {
+          if (res.data) {
+            const list = [
+              ...this.state.dataList,
+            ]
+            list.push(...res.data)
+            this.setState({
+              dataList: list
+            })
+          }
+        }).catch(err => {
+          toastShort(err)
+        })
+      }
+    }
+  }
+
   listItem = (props) => {
     return (
       <ItemView
@@ -92,7 +119,7 @@ export default class Alerts extends Component {
   }
 
   divideView = (props) => {
-    return (<View style={{height: 1/PixelRatio.get()}}></View>)
+    return (<View style={{height: 1.5/PixelRatio.get()}}></View>)
   }
 
   render() {
@@ -103,14 +130,15 @@ export default class Alerts extends Component {
         renderItem={this.listItem}
         ItemSeparatorComponent={this.divideView}
         keyExtractor={item => item.notificationId}
+        onEndReached={() => this.getMessageListPage()}
         refreshControl={
           <RefreshControl
             {...MyRefreshProps()}
             refreshing={this.state.refreshing}
-            onRefresh={() => this.getMessageList()}
+            onRefresh={() => this.onRefresh()}
           />
         }
-        ListEmptyComponent={<Text style={styles.noData} allowFontScaling={false}>{$t('notification.empty')}</Text>}
+        ListEmptyComponent={<Text style={styles.noData}>{$t('notification.empty')}</Text>}
       />
     );
   }

+ 124 - 0
Strides-APP/app/pages/alert/ListCampaign.js

@@ -0,0 +1,124 @@
+/**
+ * 活动信息列表
+ * @邠心vbe on 2023/10/24
+ */
+ import React, { Component } from 'react';
+ import { View, Text, FlatList, StyleSheet, RefreshControl, PixelRatio } from 'react-native';
+ import apiArticle from '../../api/apiArticle';
+ import { MyRefreshProps } from '../../components/ThemesConfig';
+ import { PageList } from '../Router';
+ import AlertUtil from './AlertUtil';
+import ItemCampaign from './ItemCampaign';
+
+export default class ListCampaign extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      dataList: [],
+      refreshing: false
+    };
+  }
+
+  componentDidMount() {
+    this.props.navigation.addListener('focus', () => {
+      this.getMessageList();
+    });
+  }
+
+  toDetail(item={}) {
+    if (item.articleId) {
+      startPage(PageList.viewCampaign, {id: item.articleId});
+    }
+  }
+
+  onRefresh() {
+    this.setState({
+      refreshing: true
+    })
+    this.getMessageList();
+  }
+
+  getMessageList() {
+    apiArticle.getCampaignList().then(res => {
+      if (res.data) {
+        this.setState({
+          dataList: res.data
+        })
+      }
+    }).catch(err => {
+      toastShort(err)
+    }).finally(() => {
+      this.setState({
+        refreshing: false
+      })
+    })
+  }
+
+  getMessageListPage() {
+    const count = AlertUtil.getNewsCount();
+    if (this.state.dataList.length > 0 && this.state.dataList.length < count) {
+      const last = this.state.dataList[this.state.dataList.length-1]
+      if (last.articleId) {
+        apiArticle.getCampaignList(last.articleId).then(res => {
+          if (res.data) {
+            const list = [
+              ...this.state.dataList,
+            ]
+            list.push(...res.data)
+            this.setState({
+              dataList: list
+            })
+          }
+        }).catch(err => {
+          toastShort(err)
+        })
+      }
+    }
+  }
+
+  listItem = (props) => {
+    return (
+      <ItemCampaign
+        {...props}
+        onPress={() => this.toDetail(props.item)}
+      />
+    )
+  }
+
+  divideView = (props) => {
+    return (<View style={{height: 1.5/PixelRatio.get()}}></View>)
+  }
+
+  render() {
+    return (
+      <FlatList
+        style={styles.listView}
+        data={this.state.dataList}
+        renderItem={this.listItem}
+        ItemSeparatorComponent={this.divideView}
+        keyExtractor={item => item.articleId}
+        onEndReached={() => this.getMessageListPage()}
+        refreshControl={
+          <RefreshControl
+            {...MyRefreshProps()}
+            refreshing={this.state.refreshing}
+            onRefresh={() => this.onRefresh()}
+          />
+        }
+        ListEmptyComponent={<Text style={styles.noData}>{$t('notification.empty')}</Text>}
+      />
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  listView: {
+    flex: 1
+  },
+  noData: {
+    color: textPlacehoder,
+    fontSize: 14,
+    padding: 20,
+    textAlign: 'center'
+  }
+})

+ 125 - 0
Strides-APP/app/pages/alert/ListNews.js

@@ -0,0 +1,125 @@
+/**
+ * 新闻信息列表
+ * @邠心vbe on 2023/08/17
+ */
+import React, { Component } from 'react';
+import { Text, FlatList, StyleSheet, RefreshControl, PixelRatio, View } from 'react-native';
+import apiArticle from '../../api/apiArticle';
+import { MyRefreshProps } from '../../components/ThemesConfig';
+import { PageList } from '../Router';
+import AlertUtil from './AlertUtil';
+import ItemArticle from './ItemArticle';
+
+export default class ListNews extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      dataList: [],
+      refreshing: false
+    };
+  }
+
+  componentDidMount() {
+    //this.getMessageList();
+    this.props.navigation.addListener('focus', () => {
+      this.getMessageList();
+    });
+  }
+
+  toDetail(item={}) {
+    if (item.articleId) {
+      startPage(PageList.viewArticle, {id: item.articleId});
+    }
+  }
+
+  onRefresh() {
+    this.setState({
+      refreshing: true
+    })
+    this.getMessageList();
+  }
+
+  getMessageList() {
+    apiArticle.getArticleList().then(res => {
+      if (res.data) {
+        this.setState({
+          dataList: res.data
+        })
+      }
+    }).catch(err => {
+      toastShort(err)
+    }).finally(() => {
+      this.setState({
+        refreshing: false
+      })
+    })
+  }
+
+  getMessageListPage() {
+    const count = AlertUtil.getNewsCount();
+    if (this.state.dataList.length > 0 && this.state.dataList.length < count) {
+      const last = this.state.dataList[this.state.dataList.length-1]
+      if (last.articleId) {
+        apiArticle.getArticleList(last.articleId).then(res => {
+          if (res.data) {
+            const list = [
+              ...this.state.dataList,
+            ]
+            list.push(...res.data)
+            this.setState({
+              dataList: list
+            })
+          }
+        }).catch(err => {
+          toastShort(err)
+        })
+      }
+    }
+  }
+
+  listItem = (props) => {
+    return (
+      <ItemArticle
+        {...props}
+        onPress={() => this.toDetail(props.item)}
+      />
+    )
+  }
+
+  divideView = (props) => {
+    return (<View style={{height: 1.5/PixelRatio.get()}}></View>)
+  }
+
+  render() {
+    return (
+      <FlatList
+        style={styles.listView}
+        data={this.state.dataList}
+        renderItem={this.listItem}
+        ItemSeparatorComponent={this.divideView}
+        keyExtractor={item => item.articleId}
+        onEndReached={() => this.getMessageListPage()}
+        refreshControl={
+          <RefreshControl
+            {...MyRefreshProps()}
+            refreshing={this.state.refreshing}
+            onRefresh={() => this.onRefresh()}
+          />
+        }
+        ListEmptyComponent={<Text style={styles.noData}>{$t('notification.empty')}</Text>}
+      />
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  listView: {
+    flex: 1
+  },
+  noData: {
+    color: textPlacehoder,
+    fontSize: 14,
+    padding: 20,
+    textAlign: 'center'
+  }
+})

+ 76 - 9
Strides-APP/app/pages/alert/Notification.js

@@ -4,27 +4,37 @@
  */
 import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
 import React, { Component } from 'react';
-import { StyleSheet, BackHandler } from 'react-native';
+import { StyleSheet } from 'react-native';
 import { PageList } from '../Router';
-import Alerts from './Alerts';
-import Promotions from './Promotions';
+import ListAlerts from './ListAlerts';
+import ListNews from './ListNews';
+import ListCampaign from './ListCampaign';
 import app from '../../../app.json';
 import utils from '../../utils/utils';
+import apiNotification from '../../api/apiNotification';
+import TextView from '../../components/TextView';
+import AlertUtil from './AlertUtil';
+
 
 export default class Notification extends Component {
   constructor(props) {
     super(props);
     this.state = {
-      refreshing: false
+      refreshing: false,
+      countInfo: {}
     };
     this.pageAdapter = [{
       title: $t('notification.tabPromotions'),
-      name: "Promotions",
-      component: Promotions
+      name: "News",
+      component: ListNews
+    }, {
+      title: $t('notification.tabCampaign'),
+      name: "Campaign",
+      component: ListCampaign
     }, {
       title: $t('notification.tabAlerts'),
       name: "Alerts",
-      component: Alerts
+      component: ListAlerts
     }]
     this.tabBarStyle = {
       tabBarStyle: styles.tabStyle,
@@ -40,11 +50,15 @@ export default class Notification extends Component {
   componentDidMount() {
     utils.setBackClick([this.props?.route?.name, "Alerts", "Promotions"], this.backPage)
     this.props.navigation.addListener('focus', () => {
-      this.isHide = false;
+      if (this.isHide) {
+        this.isHide = false;
+        this.getTotalCount();
+      }
     });
     this.props.navigation.addListener('blur', () => {
       this.isHide = true;
     });
+    this.getTotalCount();
     //BackHandler.addEventListener('hardwareBackPress', this.backPage)
   }
 
@@ -60,6 +74,44 @@ export default class Notification extends Component {
     }
   }
 
+  getTotalCount() {
+    apiNotification.getTabDotCount().then(res => {
+      if (res.data) {
+        this.setState({
+          countInfo: res.data
+        })
+        AlertUtil.setBadgeCount(res.data)
+      }
+    }).catch(err => {
+      if (res.data) {
+        this.setState({
+          countInfo: {}
+        })
+        AlertUtil.setBadgeCount()
+      }
+    })
+  }
+
+  getBadgeText(index) {
+    let count = 0;
+    switch (index) {
+      case 0:
+        count = this.state.countInfo?.newsUnreadCount;
+        break;
+      case 1:
+        count = this.state.countInfo?.campaignUnreadCount;
+        break
+      case 2:
+        count = this.state.countInfo?.alertUnreadCount;
+        break;
+    }
+    if (count > 0) {
+      return <TextView style={styles.badgeText}>{count}</TextView>
+    } else {
+      return <></>
+    }
+  }
+
   render() {
     const Tab = createMaterialTopTabNavigator();
     return (
@@ -78,7 +130,8 @@ export default class Notification extends Component {
             component={item.component}
             options={{
               title: item.title,
-              tabBarAllowFontScaling: false
+              tabBarAllowFontScaling: false,
+              tabBarBadge: () => this.getBadgeText(index)
             }}
           />
         )}
@@ -97,5 +150,19 @@ const styles = StyleSheet.create({
   },
   indicator: {
     backgroundColor: app.isWhitelabel ? colorPrimary : colorLight
+  },
+  badgeText: {
+    width: 20,
+    height: 20,
+    color: textLight,
+    fontSize: 10,
+    marginTop: 4,
+    marginRight: 8,
+    borderRadius: 30,
+    fontWeight: 'bold',
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'center',
+    backgroundColor: "#FF3B30"
   }
 })

+ 0 - 135
Strides-APP/app/pages/alert/Promotions.js

@@ -1,135 +0,0 @@
-/**
- * 活动信息列表
- * @邠心vbe on 2023/08/17
- */
-import React, { Component } from 'react';
-import { View, Text, FlatList, StyleSheet, RefreshControl } from 'react-native';
-import apiNotification from '../../api/apiNotification';
-import Dialog from '../../components/Dialog';
-import { MyRefreshProps } from '../../components/ThemesConfig';
-import { PageList } from '../Router';
-import ItemView from './ItemView';
-
-export default class Promotions extends Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      dataList: [],
-      refreshing: false
-    };
-    this.open = false;
-  }
-
-  componentDidMount() {
-    //this.getMessageList();
-    if (this.open) {
-      this.props.navigation.addListener('focus', () => {
-        this.getMessageList();
-      });
-    }
-  }
-
-  toDetail(item={}) {
-    if (item.notificationId) {
-      startPage(PageList.viewPromotion, {id: item.notificationId});
-    }
-  }
-
-  toDelete(item={}) {
-    if (item.notificationId) {
-      Dialog.showDialog({
-        title: $t("notification.deleteMessage"),
-        message: $t("notification.confirmDelete"),
-        ok: $t("nav.confirm"),
-        callback: (btn) => {
-          if (btn == Dialog.BUTTON_OK) {
-            this.deleteMessage(item.notificationId);
-          }
-        }
-      })
-    }
-  }
-
-  deleteMessage(id) {
-    this.setState({
-      refreshing: true
-    })
-    apiNotification.deleteMessage(id).then(res => {
-      toastShort($t("common.deleteSuccess"))
-      this.getMessageList();
-    }).catch(err => {
-      toastShort(err)
-      this.setState({
-        refreshing: false
-      })
-    })
-  }
-
-  getMessageList() {
-    this.setState({
-      refreshing: true
-    })
-    apiNotification.getNotificationList().then(res => {
-      if (res.data) {
-        this.setState({
-          dataList: res.data
-        })
-      }
-    }).catch(err => {
-      toastShort(err)
-    }).finally(() => {
-      this.setState({
-        refreshing: false
-      })
-    })
-  }
-
-  listItem = (props) => {
-    return (
-      <ItemView
-        {...props}
-        onPress={() => this.toDetail(props.item)}
-        onLongPress={() => this.toDelete(props.item)}
-      />
-    )
-  }
-
-  render() {
-    if (this.open) {
-      return (
-        <FlatList
-          style={styles.listView}
-          data={this.state.dataList}
-          renderItem={this.listItem}
-          keyExtractor={item => item.notificationId}
-          refreshControl={
-            <RefreshControl
-              {...MyRefreshProps()}
-              refreshing={this.state.refreshing}
-              onRefresh={() => this.getMessageList()}
-            />
-          }
-          ListEmptyComponent={<Text style={styles.noData} allowFontScaling={false}>{$t('notification.empty')}</Text>}
-        />
-      );
-    } else {
-      return (
-        <View>
-          <Text style={ui.noData} allowFontScaling={false}> Coming soon </Text>
-        </View>
-      );
-    }
-  }
-}
-
-const styles = StyleSheet.create({
-  listView: {
-    flex: 1
-  },
-  noData: {
-    color: textPlacehoder,
-    fontSize: 14,
-    padding: 20,
-    textAlign: 'center'
-  }
-})

+ 29 - 10
Strides-APP/app/pages/alert/Message.js → Strides-APP/app/pages/alert/ViewAlerts.js

@@ -3,14 +3,15 @@
  * @邠心vbe on 2023/08/17
  */
 import React, { Component } from 'react';
-import { View, Text, StyleSheet, ScrollView } from 'react-native';
+import { View, StyleSheet, ScrollView } from 'react-native';
 import apiNotification from '../../api/apiNotification';
-import Button from '../../components/Button';
+import Button, { ElevationObject } from '../../components/Button';
 import Dialog from '../../components/Dialog';
+import HeaderTitle from '../../components/HeaderTitle';
 import TextView from '../../components/TextView';
 import { PageList } from '../Router';
 
-export default class Message extends Component {
+export default class ViewAlerts extends Component {
   constructor(props) {
     super(props);
     this.state = {
@@ -41,6 +42,7 @@ export default class Message extends Component {
         this.setState({
           messageInfo: res.data
         });
+        this.setPageTitle();
       }
     }).catch(err => {
       toastShort(err);
@@ -49,6 +51,14 @@ export default class Message extends Component {
     })
   }
 
+  setPageTitle() {
+    if (this.state.messageInfo.notificationTitle) {
+      this.props.navigation.setOptions({
+        headerTitle: () => (<HeaderTitle title={this.state.messageInfo.notificationTitle}/>)
+      })
+    }
+  }
+
   submitFeedback() {
     startPage(PageList.feedback);
   }
@@ -61,11 +71,18 @@ export default class Message extends Component {
             style={styles.textTitle}>
             {this.state.messageInfo.notificationTitle}
           </TextView>
-          <TextView
-            style={styles.textDate}
-            numberOfLines={1}>
-            {this.state.messageInfo.createTime}
-          </TextView>
+          <View style={ui.flexc}>
+            <MaterialCommunityIcons
+              name="clock-time-four-outline"
+              color={textSecondary}
+              size={12}/>
+            <TextView
+              style={styles.textDate}
+              numberOfLines={1}>
+              {this.state.messageInfo.createTime}
+            </TextView>
+          </View>
+          
         </View>
         <ScrollView
           style={ui.flex1}>
@@ -95,15 +112,17 @@ const styles = StyleSheet.create({
   textTitle: {
     color: textPrimary,
     fontSize: 18,
-    fontWeight: 'bold'
+    fontWeight: 'bold',
+    paddingBottom: 2
   },
   textDate: {
     color: textSecondary,
     fontSize: 10,
-    paddingTop: 1
+    paddingLeft: 2
   },
   header: {
     padding: 16,
+    ...ElevationObject(1),
     backgroundColor: pageBackground
   },
   textMessage: {

+ 206 - 0
Strides-APP/app/pages/alert/ViewArticle.js

@@ -0,0 +1,206 @@
+/**
+ * 文章详情
+ * @邠心vbe on 2023/10/24
+ */
+import React, { Component } from 'react';
+import { View,  StyleSheet, Image, ScrollView, Linking } from 'react-native';
+import Swiper from 'react-native-swiper';
+import apiArticle from '../../api/apiArticle';
+import { ElevationObject } from '../../components/Button';
+import HeaderTitle from '../../components/HeaderTitle';
+import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
+
+export default class ViewArticle extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      id: "",
+      messageInfo: {
+        articleTypeName: "",
+        articleTitle: "",
+        articleContent: ""
+      }
+    };
+  }
+
+  componentDidMount() {
+    if (this.props.route?.params?.id) {
+      this.setState({
+        id: this.props.route?.params?.id
+      }, () => {
+        this.readMessage();
+      })
+    }
+  }
+
+  readMessage() {
+    Dialog.showProgressDialog();
+    apiArticle.readMessage(this.state.id).then(res => {
+      if (res.data) {
+        this.setState({
+          messageInfo: res.data
+        });
+        this.setPageTitle();
+      }
+    }).catch(err => {
+      toastShort(err);
+    }).finally(() => {
+      Dialog.dismissLoading();
+    })
+  }
+
+  setPageTitle() {
+    if (this.state.messageInfo.articleTitle) {
+      this.props.navigation.setOptions({
+        headerTitle: () => (<HeaderTitle title={this.state.messageInfo.articleTitle}/>)
+      })
+    }
+  }
+
+  accessLink(url) {
+    Linking.openURL(utils.getImageUrl(url))
+  }
+
+
+  render() {
+    return (
+      <ScrollView
+        style={styles.container}
+        contentContainerStyle={$padding(0,0,32)}
+        stickyHeaderIndices={[utils.isNotEmpty(this.state.messageInfo.articleImages) ? 1 : 0]}>
+        { utils.isNotEmpty(this.state.messageInfo.articleImages) &&
+          <Swiper
+            style={{height: $width}}
+            autoplay={true}
+            autoplayTimeout={5}
+            renderPagination={() => <></>}>
+            { this.state.messageInfo.articleImages.map((item, index) => {
+              return (
+                <Image
+                  key={index}
+                  style={{width: $width, height: $width}}
+                  source={{uri: utils.getImageUrl(item.articleImagePath)}}/>
+              );
+            })}
+          </Swiper>
+        }
+        <View style={styles.header}>
+          <TextView
+            style={styles.textTitle}>
+            {this.state.messageInfo.articleTitle}
+          </TextView>
+          <View style={ui.flexc}>
+            <MaterialCommunityIcons
+              name="clock-time-four-outline"
+              color={textSecondary}
+              size={12}/>
+            <TextView
+              style={styles.textDate}
+              numberOfLines={1}>
+              {this.state.messageInfo.createTime}
+            </TextView>
+            <MaterialCommunityIcons
+              name="eye-check-outline"
+              size={12}
+              color={textPrimary}/>
+            <TextView
+              style={styles.textView}
+              numberOfLines={1}>
+              {this.state.messageInfo.articleViews}
+            </TextView>
+          </View>
+          <View style={ui.flex}>
+            <TextView 
+              style={styles.labelTypeText}
+              numberOfLines={1}>
+              {this.state.messageInfo.articleTypeName}
+            </TextView>
+          </View>
+        </View>
+        <TextView
+          style={styles.textMessage}>
+          {this.state.messageInfo.articleContent}
+        </TextView>
+        { utils.isNotEmpty(this.state.messageInfo.articleLinks) &&
+          <>
+            <TextView style={styles.textLinkTitle}>{$t("notification.labelLinks")}</TextView>
+            { this.state.messageInfo.articleLinks.map((item, index) =>
+              <View style={styles.itemLink} key={index}>
+                <TextView style={styles.linkIndex}>{index + 1}.</TextView>
+                <TextView 
+                  style={styles.linkHyper}
+                  onPress={() => this.accessLink(item.articleLink)}>{item.articleLinkName}</TextView>
+              </View>
+            )}
+          </>
+        }
+        <EndView/>
+      </ScrollView>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: pageBackground
+  },
+  textTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold',
+    paddingBottom: 2
+  },
+  textDate: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 10,
+    paddingLeft: 2
+  },
+  textView: {
+    color: textSecondary,
+    fontSize: 10,
+    paddingLeft: 4
+  },
+  header: {
+    padding: 16,
+    ...ElevationObject(1),
+    backgroundColor: pageBackground
+  },
+  textMessage: {
+    color: textPrimary,
+    fontSize: 14,
+    padding: 16
+  },
+  labelTypeText: {
+    fontSize: 11,
+    borderWidth: 1,
+    borderRadius: 4,
+    borderColor: colorPrimary,
+    marginTop: 8,
+    ...$padding(2, 6)
+  },
+  textLinkTitle: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold',
+    padding: 16
+  },
+  itemLink: {
+    ...$padding(0, 16, 8),
+    flexDirection: 'row'
+  },
+  linkIndex: {
+    fontSize: 14,
+    paddingRight: 2
+  },
+  linkHyper: {
+    ...ui.link,
+    fontSize: 14,
+    textDecorationLine: 'underline'
+  },
+  linkActive: {
+    color: "#FF3B30"
+  }
+})

+ 250 - 0
Strides-APP/app/pages/alert/ViewCampaign.js

@@ -0,0 +1,250 @@
+/**
+ * 活动详情
+ * @邠心vbe on 2023/08/17
+ */
+ import React, { Component } from 'react';
+ import { View,  StyleSheet, Image, ScrollView, Linking } from 'react-native';
+ import Swiper from 'react-native-swiper';
+ import apiArticle from '../../api/apiArticle';
+import { ElevationObject } from '../../components/Button';
+ import HeaderTitle from '../../components/HeaderTitle';
+ import TextView from '../../components/TextView';
+ import utils from '../../utils/utils';
+
+export default class ViewCampaign extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      id: "",
+      messageInfo: {
+        articleTypeName: "",
+        articleTitle: "",
+        articleContent: ""
+      }
+    };
+  }
+
+  componentDidMount() {
+    if (this.props.route?.params?.id) {
+      this.setState({
+        id: this.props.route?.params?.id
+      }, () => {
+        this.readMessage();
+      })
+    }
+  }
+
+  readMessage() {
+    Dialog.showProgressDialog();
+    apiArticle.readMessage(this.state.id).then(res => {
+      if (res.data) {
+        this.setState({
+          messageInfo: res.data
+        });
+        this.setPageTitle();
+      }
+    }).catch(err => {
+      toastShort(err);
+    }).finally(() => {
+      Dialog.dismissLoading();
+    })
+  }
+
+  setPageTitle() {
+    if (this.state.messageInfo.articleTitle) {
+      this.props.navigation.setOptions({
+        headerTitle: () => (<HeaderTitle title={this.state.messageInfo.articleTitle}/>)
+      })
+    }
+  }
+
+  accessLink(url) {
+    Linking.openURL(utils.getImageUrl(url))
+  }
+
+  getColorByType(type) {
+    switch (type) {
+      case "Ended":
+        return {
+          backgroundColor: "#E11919"
+        }
+      case "Upcoming":
+        return {
+          backgroundColor: "#FFAA2C"
+        }
+    }
+  }
+
+  render() {
+    return (
+      <ScrollView
+        style={styles.container}
+        contentContainerStyle={$padding(0,0,32)}
+        stickyHeaderIndices={[utils.isNotEmpty(this.state.messageInfo.articleImages) ? 1 : 0]}>
+        { utils.isNotEmpty(this.state.messageInfo.articleImages) &&
+          <Swiper
+            style={{height: $width}}
+            autoplay={true}
+            autoplayTimeout={5}
+            renderPagination={() => <></>}>
+            { this.state.messageInfo.articleImages.map((item, index) => {
+              return (
+                <Image
+                  key={index}
+                  style={{width: $width, height: $width}}
+                  source={{uri: utils.getImageUrl(item.articleImagePath)}}/>
+              );
+            })}
+          </Swiper>
+        }
+        <View style={styles.header}>
+          <View>
+            <TextView 
+              style={[styles.labelTypeText, this.getColorByType(this.state.messageInfo.campaignMark)]}
+              numberOfLines={1}>
+              {this.state.messageInfo.campaignMark}
+            </TextView>
+            <TextView
+              style={styles.textTitle}
+              numberOfLines={2}
+              ellipsizeMode="tail">
+              <TextView
+                style={styles.hideText}
+                numberOfLines={1}>
+                {this.state.messageInfo.campaignMark}
+              </TextView>
+              {this.state.messageInfo.articleTitle}
+            </TextView>
+          </View>
+          <View style={ui.flexc}>
+            <MaterialCommunityIcons
+              name="clock-time-four-outline"
+              color={textSecondary}
+              size={12}/>
+            <TextView
+              style={styles.textDate}
+              numberOfLines={1}>
+              {this.state.messageInfo.createTime}
+            </TextView>
+            <MaterialCommunityIcons
+              name="eye-check-outline"
+              size={12}
+              color={textPrimary}/>
+            <TextView
+              style={styles.textView}
+              numberOfLines={1}>
+              {this.state.messageInfo.articleViews}
+            </TextView>
+          </View>
+        </View>
+        <TextView style={styles.textLinkTitle}>{$t("notification.labelSummary")}</TextView>
+        <TextView
+          style={styles.textSummary}>
+          {$t("notification.startTime")}
+          {this.state.messageInfo.startTime}
+        </TextView>
+        <TextView
+          style={styles.textSummary}>
+          {$t("notification.endTime")}
+          {this.state.messageInfo.endTime}
+        </TextView>
+        <TextView style={styles.textLinkTitle}>{$t("notification.labelDetails")}</TextView>
+        <TextView
+          style={styles.textMessage}>
+          {this.state.messageInfo.articleContent}
+        </TextView>
+        { utils.isNotEmpty(this.state.messageInfo.articleLinks) &&
+          <>
+            <TextView style={styles.textLinkTitle}>{$t("notification.labelLinks")}</TextView>
+            { this.state.messageInfo.articleLinks.map((item, index) =>
+              <View style={styles.itemLink} key={index}>
+                <TextView style={styles.linkIndex}>{index + 1}.</TextView>
+                <TextView 
+                  style={styles.linkHyper}
+                  onPress={() => this.accessLink(item.articleLink)}>{item.articleLinkName}</TextView>
+              </View>
+            )}
+          </>
+        }
+        <EndView/>
+      </ScrollView>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: pageBackground
+  },
+  textTitle: {
+    color: textPrimary,
+    fontSize: 18,
+    fontWeight: 'bold',
+    paddingBottom: 2
+  },
+  textDate: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 10,
+    paddingLeft: 2
+  },
+  textView: {
+    color: textSecondary,
+    fontSize: 10,
+    paddingLeft: 4
+  },
+  header: {
+    padding: 16,
+    ...ElevationObject(1),
+    backgroundColor: pageBackground
+  },
+  textSummary: {
+    color: textPrimary,
+    fontSize: 14,
+    ...$padding(0, 16, 8)
+  },
+  textMessage: {
+    color: textPrimary,
+    fontSize: 14,
+    ...$padding(0, 16, 16)
+  },
+  labelTypeText: {
+    color: textLight,
+    height: 16,
+    fontSize: 10,
+    borderRadius: 4,
+    marginTop: 3,
+    ...$padding(0, 6),
+    position: 'absolute',
+    backgroundColor: "#1ABD00"
+  },
+  hideText: {
+    height: 16,
+    opacity: 0,
+    fontSize: 10,
+    ...$padding(2, 8),
+  },
+  textLinkTitle: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold',
+    padding: 16
+  },
+  itemLink: {
+    ...$padding(0, 16, 8),
+    flexDirection: 'row'
+  },
+  linkIndex: {
+    fontSize: 14,
+    paddingRight: 2
+  },
+  linkHyper: {
+    ...ui.link,
+    fontSize: 14,
+    textDecorationLine: 'underline'
+  },
+  linkActive: {
+    color: "#FF3B30"
+  }
+})

+ 3 - 3
Strides-APP/app/pages/home/Drawer.js

@@ -3,14 +3,14 @@
  * @邠心vbe on 2021/03/23
  */
 import React, { Component } from 'react';
-import {View, Text, StyleSheet, Image, Pressable, BackHandler, Linking, Touchable} from 'react-native';
+import {View, Text, StyleSheet, Image} from 'react-native';
 import { createDrawerNavigator, DrawerContentScrollView } from '@react-navigation/drawer';
 import { Styles } from '../../components/Toolbar';
 import app from '../../../app.json';
 import Maps from './Home';
 import { PageList } from '../Router';
 import Dialog from '../../components/Dialog';
-import { host, setAccessToken } from '../../api/http';
+import { setAccessToken } from '../../api/http';
 import { getStorageJsonSync, setStorage, setStorageJson } from '../../utils/storage';
 import Button from '../../components/Button';
 import { AutoLogin } from '../sign/Login';
@@ -201,7 +201,7 @@ const DrawerContent = ({isLogin, userInfo, onLogout, notificationCount=0, naviga
         ? <TouchableWithoutFeedback onPress={() => startPage(PageList.profile)}>
             <Image
               style={styles.avatar}
-              source={{uri: host + userInfo.photoUrl}}/>
+              source={{uri: utils.getImageUrl(userInfo.photoUrl)}}/>
           </TouchableWithoutFeedback>
         : <TouchableWithoutFeedback onPress={() => startPage(PageList.login)}>
             <Image

+ 23 - 6
Strides-APP/app/pages/member/MembersList.js

@@ -3,7 +3,7 @@
  * @邠心vbe on 2023/07/14
  */
 import React, { Component } from 'react';
-import { View, Text, StyleSheet, Pressable } from 'react-native';
+import { View, Text, StyleSheet, Pressable, Image } from 'react-native';
 import apiMember from '../../api/apiMember';
 import { ElevationObject } from '../../components/Button';
 import TextView from '../../components/TextView';
@@ -66,10 +66,17 @@ export default class MembersList extends Component {
             //startPage(PageList.editVehicle, {id: item.vehiclePk});
           }}>
           <View style={styles.itemBackground}>
-            <MaterialIcons
-              name="card-membership"
-              color={colorAccent}
-              size={56}/>
+            { utils.isNotEmpty(item.groupLogo)
+            ? <Image
+                source={{uri: utils.getImageUrl(item.groupLogo)}}
+                resizeMode="contain"
+                style={styles.groupLogo}/>
+            : <MaterialIcons
+                name="card-membership"
+                color={colorAccent}
+                style={styles.groupIcon}
+                size={52}/>
+            }
           </View>
           <View style={styles.memberItem}>
             { utils.isEmpty(item.groupType)
@@ -146,7 +153,17 @@ const styles = StyleSheet.create({
   itemBackground: {
     right: 0,
     bottom: 0,
-    opacity: 0.06,
     position: 'absolute'
+  },
+  groupIcon: {
+    margin: 4,
+    opacity: 0.08
+  },
+  groupLogo: {
+    width: 48,
+    height: 48,
+    margin: 8,
+    opacity: .8,
+    borderRadius: 6
   }
 })

+ 1 - 2
Strides-APP/app/pages/my/EditProfile.js

@@ -8,7 +8,6 @@ import ImagePicker from 'react-native-image-crop-picker';
 import Modal from 'react-native-modal';
 import apiUpload from '../../api/apiUpload';
 import apiUser from '../../api/apiUser';
-import { host } from '../../api/http';
 import { ModalProps } from '../../components/BottomModal';
 import Button, { ViewHeight } from '../../components/Button';
 import { CountryDropCode, CountryDropNum, GetCountryList } from '../../components/CountryIcon';
@@ -248,7 +247,7 @@ export default class EditProfile extends Component {
           { userInfo.photoUrl
           ? <Image
               style={styles.avatar}
-              source={{uri: host + userInfo.photoUrl}}/>
+              source={{uri: utils.getImageUrl(userInfo.photoUrl)}}/>
           : <Image
               style={styles.avatar}
               source={require('../../images/user/ic-avatar-default.png')}/>

+ 4 - 3
Strides-APP/app/pages/my/Feedback.js

@@ -6,7 +6,6 @@ import React from 'react';
 import { View, Text, StyleSheet, ScrollView, TextInput, Image, Pressable, FlatList } from 'react-native';
 import Button from '../../components/Button';
 import apiUpload from '../../api/apiUpload';
-import { host } from '../../api/http';
 import apiUser from '../../api/apiUser';
 import Dialog from '../../components/Dialog';
 import ImagePicker from 'react-native-image-crop-picker';
@@ -14,6 +13,7 @@ import Dropdown from '../../components/Dropdown';
 import Modal from 'react-native-modal';
 import { UploadThemes } from '../../components/ThemesConfig';
 import apiBase from '../../api/apiBase.js';
+import utils from '../../utils/utils';
 
 const options = {
   cropping: false,
@@ -219,7 +219,8 @@ export default class Feedback extends React.Component {
     return (
       <ScrollView
         style={styles.container}
-        keyboardShouldPersistTaps={isIOS ? 'never' : 'handled'}>
+        keyboardShouldPersistTaps={isIOS ? 'never' : 'handled'}
+        contentInsetAdjustmentBehavior='automatic'>
         <View style={styles.headerView}>
           <View style={ui.flex1}>
             <Text style={styles.title}>{$t('feedback.tipsSomething')}</Text>
@@ -276,7 +277,7 @@ export default class Feedback extends React.Component {
                     : <Image
                         style={styles.uploadIcon}
                         defaultSource={require('../../images/icon/icon-upload-default.png')}
-                        source={{uri: host + item}}/>
+                        source={{uri: utils.getImageUrl(item)}}/>
                     }
                   </Pressable>
                 );

+ 65 - 25
Strides-APP/app/pages/my/Profile.js

@@ -5,15 +5,17 @@
 import React, { Component } from 'react';
 import { View, Text, StyleSheet, Image, ScrollView, Switch, Pressable } from 'react-native';
 import apiUser from '../../api/apiUser';
-import { host } from '../../api/http';
+import { setAccessToken } from '../../api/http';
 import Button, { ElevationObject } from '../../components/Button';
 import Dialog from '../../components/Dialog';
 import { StationBack } from '../../components/Toolbar';
 import ProfileBackground from '../../icons/ProfileBackground';
 import TopChargeBackground from '../../icons/TopChargeBackground';
+import { getStorageJsonSync, setStorage, setStorageJson } from '../../utils/storage';
 import utils from '../../utils/utils';
 import { PageList } from '../Router';
 import VehicleList from './VehicleList';
+
 export default class Profile extends Component {
   constructor(props) {
     super(props);
@@ -89,9 +91,9 @@ export default class Profile extends Component {
 
   deleteAccount() {
     Dialog.showDialog({
-      title: 'Delete My Account',
-      message: 'Are you sure you want to delete your account? This operation cannot be revoke.',
-      ok: 'CONFIRM',
+      title: $t('profile.deleteAccount'),
+      message: $t('profile.confirmDeleteAccount'),
+      ok: $t('nav.confirm'),
       callback: button => {
         if (button == Dialog.BUTTON_OK) {
           this.deleteMyAccount();
@@ -103,7 +105,7 @@ export default class Profile extends Component {
   deleteMyAccount() {
     Dialog.showProgressDialog();
     apiUser.deleteAccount().then(res => {
-      toastShort('Successfully deleted!')
+      toastShort($t('profile.deleteAccountSuccess'))
       Dialog.dismissLoading();
       setTimeout(() => {
         startPage(PageList.login);
@@ -114,6 +116,38 @@ export default class Profile extends Component {
     })
   }
 
+  logout() {
+    Dialog.showDialog({
+      title: $t('profile.signOut'),
+      message: $t('profile.tipSignOut'),
+      callback: btn => {
+        if (btn == 'ok') {
+          Dialog.showProgressDialog();
+          setTimeout(() => {
+            this.requestLogout();
+          }, 500);
+        }
+      }
+    })
+  }
+
+  async requestLogout() {
+    const data = await getStorageJsonSync('loginData');
+    if (data && data.email) {
+      delete data.password
+      setStorageJson('loginData', data);
+      setStorage('RegisterTokenDate', "");
+    }
+    global.userInfo = {}
+    /*this.setState({
+      isLogin: false,
+      userInfo: {}
+    });*/
+    setAccessToken('');
+    goBack();
+    Dialog.dismissLoading();
+  }
+
   render() {
     return (
       <ScrollView style={styles.container}>
@@ -133,10 +167,10 @@ export default class Profile extends Component {
             { userInfo.photoUrl
             ? <Image
                 style={styles.avatar}
-                source={{uri: host + userInfo.photoUrl}}/>
+                source={{uri: utils.getImageUrl(userInfo.photoUrl)}}/>
             : <Image
                 style={styles.avatar}
-                source={require('../../images/user/icon-default.jpg')}/>
+                source={require('../../images/user/ic-avatar-default.png')}/>
             }
             <View style={styles.infoContent}>
               <Text
@@ -148,7 +182,7 @@ export default class Profile extends Component {
             </View>
             <FontAwesome
               size={34}
-              color='#333'
+              color={colorDark}
               name='angle-right'/>
           </Pressable>
           <StationBack bottom={52} scale={0.8}/>
@@ -174,7 +208,7 @@ export default class Profile extends Component {
                 startPage(PageList.wallet);
               }}>
               <Text style={styles.cardPrimary}>{currency}{userInfo.credit}</Text>
-              <Text style={styles.cardLabel}>Credits</Text>
+              <Text style={styles.cardLabel}>Credit Wallet</Text>
             </Pressable>
           </View>
         </View>
@@ -182,14 +216,14 @@ export default class Profile extends Component {
         <View style={styles.titleView}>
           <Text style={styles.title}>My Vehicles</Text>
           <Button
-            style={{backgroundColor: '#fff'}}
+            style={{backgroundColor: colorLight}}
             borderRadius={3}
             viewStyle={styles.titleAdd}
             onClick={() => {
               startPage(PageList.addVehicle)
             }}>
-            <Entypo name='plus' size={14} color='#333'/>
-            <Text style={{fontSize: 12, paddingLeft: 1, color: '#333'}}>Add Vehicle</Text>
+            <Entypo name='plus' size={14} color={colorDark}/>
+            <Text style={{fontSize: 12, paddingLeft: 1, color: textPrimary}}>Add Vehicle</Text>
           </Button>
         </View>
         <View style={styles.verhicleList}>
@@ -207,12 +241,12 @@ export default class Profile extends Component {
         {/* <View style={styles.titleView}>
           <Text style={styles.title}>My Cards</Text>
           <Button
-            textColor='#333'
+            textColor={textPrimary}
             style={styles.titleAdd}
             onClick={() => {
               startPage(PageList.addCard)
             }}>
-            <Entypo name='plus' size={14} color='#333'/>
+            <Entypo name='plus' size={14} color={colorDark}/>
             <Text style={{fontSize: 12, paddingLeft: 1}}>Add Card</Text>
           </Button>
         </View>
@@ -244,7 +278,7 @@ export default class Profile extends Component {
             onPress={() => {
               this.changeSwitch("notifyLowBalance", !userInfo.notifyLowBalance);
             }}>
-            <Text style={styles.notiLabel}>Notify me when credits is low balance</Text>
+            <Text style={styles.notiLabel}>Notify me when wallet is low balance</Text>
             <Switch 
               value={this.state.userInfo.notifyLowBalance}
               trackColor={isIOS ? { false: "#B2B2B2", true: colorAccent } : null}
@@ -270,9 +304,15 @@ export default class Profile extends Component {
         <Button
           style={styles.deleteButton}
           text="DELETE MY ACCOUNT"
-          textColor="#fff"
+          textColor={textButton}
           onClick={() => this.deleteAccount()}
         />
+        <Button
+          style={styles.deleteButton}
+          text="Logout"
+          textColor={textButton}
+          onClick={() => this.logout()}
+        />
       </ScrollView>
     );
   }
@@ -281,7 +321,7 @@ export default class Profile extends Component {
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: 'white'
+    backgroundColor: pageBackground
   },
   headerView: {
     paddingBottom: 72
@@ -305,7 +345,7 @@ const styles = StyleSheet.create({
     height: 66,
     borderWidth: 2,
     borderRadius: 80,
-    borderColor: '#fff'
+    borderColor: colorLight
   },
   infoContent: {
     flex: 1,
@@ -318,7 +358,7 @@ const styles = StyleSheet.create({
     paddingBottom: 1.5,
   },
   userText: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 13,
     paddingTop: 1.5
   },
@@ -331,7 +371,7 @@ const styles = StyleSheet.create({
     ...ElevationObject(2),
     alignItems: 'center',
     flexDirection: 'row',
-    backgroundColor: 'white',
+    backgroundColor: colorLight,
   },
   cardItem: {
     flex: 1,
@@ -352,12 +392,12 @@ const styles = StyleSheet.create({
     alignItems: 'center'
   },
   cardPrimary: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 18,
     paddingBottom: 2
   },
   cardLabel: {
-    color: '#333',
+    color: textPrimary,
     fontSize: 13,
   },
   titleView: {
@@ -373,7 +413,7 @@ const styles = StyleSheet.create({
   },
   titleAdd: {
     padding: 8,
-    color: '#333',
+    color: textPrimary,
     alignItems: 'center',
     flexDirection: 'row'
   },
@@ -397,7 +437,7 @@ const styles = StyleSheet.create({
     paddingRight: 16,
     borderColor: '#f5f5f5',
     borderWidth: 1,
-    backgroundColor: 'white',
+    backgroundColor: colorLight,
     ...ElevationObject(1.5)
   },
   notificationItem: {
@@ -408,7 +448,7 @@ const styles = StyleSheet.create({
   },
   notiLabel: {
     flex: 1,
-    color: '#333',
+    color: textPrimary,
     fontSize: 14
   },
   divide: {

+ 2 - 2
Strides-APP/app/pages/my/ProfileV2.js

@@ -5,7 +5,7 @@
 import React, { Component } from 'react';
 import { View, StyleSheet, Image, ScrollView } from 'react-native';
 import apiUser from '../../api/apiUser';
-import { host, setAccessToken } from '../../api/http';
+import { setAccessToken } from '../../api/http';
 import Button, { ElevationObject } from '../../components/Button';
 import Dialog from '../../components/Dialog';
 import TextView from '../../components/TextView';
@@ -112,7 +112,7 @@ export default class ProfileV2 extends Component {
           { this.state.userInfo.photoUrl
           ? <Image
               style={styles.avatar}
-              source={{uri: host + this.state.userInfo.photoUrl}}/>
+              source={{uri: utils.getImageUrl(this.state.userInfo.photoUrl)}}/>
           : <Image
               style={styles.avatar}
               source={require('../../images/user/ic-avatar-default.png')}/>

+ 8 - 5
Strides-APP/app/pages/sign/Register.js

@@ -14,12 +14,12 @@ import { RegisterDialog } from '../charge/InfoDialog';
 import Dropdown from '../../components/Dropdown';
 import ImagePicker from 'react-native-image-crop-picker';
 import apiUpload from '../../api/apiUpload';
-import { host } from '../../api/http';
 import CheckBox from '@react-native-community/checkbox';
 import { ModalProps } from '../../components/BottomModal';
 import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import StrengthView from './StrengthView';
 import { UploadThemes } from '../../components/MyRefreshControl';
+import utils from '../../utils/utils';
 
 const options = {
   width: 300,
@@ -205,7 +205,10 @@ export default class Register extends React.Component {
   }
 
   uploadImage(index) {
-    ImagePicker.openPicker(options).then(image => {
+    ImagePicker.openPicker({
+      ...options,
+      cropperToolbarTitle: $t('common.cropperTitle')
+    }).then(image => {
       if (image.path) {
         apiUpload.uploadImage(image.path, image.mime, 'PDVL').then(res => {
           if (res.success && res.data.picturePath) {
@@ -221,8 +224,8 @@ export default class Register extends React.Component {
           toastShort(err);
         });
       }
-    }).catch(err => {
-      //console.log(err);
+    }).catch(err1 => {
+      //console.log(err1);
     });
   }
 
@@ -482,7 +485,7 @@ const UploadView = ({url, onPress}) => (
     : <Image
         style={styles.uploadIcon}
         defaultSource={require('../../images/icon/icon-upload-default.png')}
-        source={{uri: host + url}}/>
+        source={{uri: utils.getImageUrl(url)}}/>
     }
   </Pressable>
 )

+ 2 - 3
Strides-APP/app/pages/sign/RegisterDriver.js

@@ -6,19 +6,18 @@ import React from 'react';
 import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
 import apiUser from '../../api/apiUser';
 import Button from '../../components/Button';
-import { PageList } from '../Router';
 import Dialog from '../../components/Dialog';
 import Modal from 'react-native-modal';
 import { RegisterDialog } from '../charge/InfoDialog';
 import Dropdown from '../../components/Dropdown';
 import ImagePicker from 'react-native-image-crop-picker';
 import apiUpload from '../../api/apiUpload';
-import { host } from '../../api/http';
 import { ModalProps } from '../../components/BottomModal';
 import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import StrengthView from './StrengthView';
 import CheckBox from '../../components/CheckBox';
 import { UploadThemes } from '../../components/ThemesConfig';
+import utils from '../../utils/utils';
 
 const options = {
   width: 300,
@@ -444,7 +443,7 @@ export const UploadView = ({style=styles.uploadView, imageStyle=styles.uploadIco
         style={imageStyle}
         resizeMode="cover"
         defaultSource={require('../../images/icon/icon-upload-default.png')}
-        source={{uri: host + url}}/>
+        source={{uri: utils.getImageUrl(url)}}/>
     }
   </Pressable>
 )

+ 4 - 4
Strides-APP/app/pages/sign/RegisterV2.js

@@ -3,8 +3,8 @@
  * @邠心vbe on 2023/02/01
  */
 import React from 'react';
-import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
-import { BackButton, BackIcon, Styles } from '../../components/Toolbar';
+import { View, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import { BackButton, Styles } from '../../components/Toolbar';
 import apiUser from '../../api/apiUser';
 import Button from '../../components/Button';
 import { PageList } from '../Router';
@@ -14,13 +14,13 @@ import { RegisterDialog } from '../charge/InfoDialog';
 import Dropdown from '../../components/Dropdown';
 import ImagePicker from 'react-native-image-crop-picker';
 import apiUpload from '../../api/apiUpload';
-import { host } from '../../api/http';
 import { ModalProps } from '../../components/BottomModal';
 import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import StrengthView from './StrengthView';
 import CheckBox from '../../components/CheckBox';
 import { UploadThemes } from '../../components/ThemesConfig';
 import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
 
 const options = {
   width: 300,
@@ -490,7 +490,7 @@ const UploadView = ({url, onPress}) => (
     : <Image
         style={styles.uploadIcon}
         defaultSource={require('../../images/icon/icon-upload-default.png')}
-        source={{uri: host + url}}/>
+        source={{uri: utils.getImageUrl(url)}}/>
     }
   </Pressable>
 )

+ 3 - 3
Strides-APP/app/pages/sign/RegisterV3.js

@@ -3,7 +3,7 @@
  * @邠心vbe on 2023/02/01
  */
 import React from 'react';
-import { View, Text, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
+import { View, ScrollView, StyleSheet, TextInput, Pressable, Image } from 'react-native';
 import apiUser from '../../api/apiUser';
 import Button from '../../components/Button';
 import { PageList } from '../Router';
@@ -13,13 +13,13 @@ import { RegisterDialog } from '../charge/InfoDialog';
 import Dropdown from '../../components/Dropdown';
 import ImagePicker from 'react-native-image-crop-picker';
 import apiUpload from '../../api/apiUpload';
-import { host } from '../../api/http';
 import { ModalProps } from '../../components/BottomModal';
 import { CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import StrengthView from './StrengthView';
 import CheckBox from '../../components/CheckBox';
 import { UploadThemes } from '../../components/ThemesConfig';
 import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
 
 const options = {
   width: 300,
@@ -483,7 +483,7 @@ const UploadView = ({url, onPress}) => (
     : <Image
         style={styles.uploadIcon}
         defaultSource={require('../../images/icon/icon-upload-default.png')}
-        source={{uri: host + url}}/>
+        source={{uri: utils.getImageUrl(url)}}/>
     }
   </Pressable>
 )

+ 2 - 2
Strides-APP/app/pages/sign/RegisterV4.js

@@ -12,12 +12,12 @@ import app from '../../../app.json';
 import Dropdown from '../../components/Dropdown';
 import ImagePicker from 'react-native-image-crop-picker';
 import apiUpload from '../../api/apiUpload';
-import { host } from '../../api/http';
 import { CountryDropCode, CountryDropNum, GetCountryList } from '../../components/CountryIcon';
 import StrengthView from './StrengthView';
 import CheckBox from '../../components/CheckBox';
 import { UploadThemes } from '../../components/ThemesConfig';
 import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
 
 const options = {
   width: 300,
@@ -594,7 +594,7 @@ const UploadView = ({url, onPress}) => (
     : <Image
         style={styles.uploadIcon}
         defaultSource={require('../../images/icon/icon-upload-default.png')}
-        source={{uri: host + url}}/>
+        source={{uri: utils.getImageUrl(url)}}/>
     }
   </Pressable>
 )

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

@@ -1,5 +1,6 @@
 import { Linking } from "react-native";
 import { showLocation } from 'react-native-map-link'
+import { host } from '../api/http';
 import apiUser from "../api/apiUser";
 import { getStorageSync, setStorage } from "./storage";
 
@@ -281,5 +282,12 @@ export default {
       names: routeNames,
       callback: func
     }
+  },
+  getImageUrl(path) {
+    if (path && path.indexOf("http") >= 0) {
+      return path;
+    } else {
+      return host + path;
+    }
   }
 }

+ 1 - 0
Strides-APP/package.json

@@ -64,6 +64,7 @@
     "react-native-screens": "^3.24.0",
     "react-native-share": "^9.2.3",
     "react-native-svg": "^13.13.0",
+    "react-native-swiper": "^1.6.0",
     "react-native-tab-view": "^3.5.2",
     "react-native-vector-icons": "^10.0.0",
     "react-native-view-shot": "^3.1.2",