Browse Source

[new]增加版本更新提醒功能

zk 1 year ago
parent
commit
6c1da1d90c

+ 6 - 0
android/app/src/main/AndroidManifest.xml

@@ -20,6 +20,12 @@
     <queries>
         <package android:name="com.tencent.mobileqq" />
         <package android:name="com.tencent.mm" />
+
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <category android:name="android.intent.category.BROWSABLE" />
+            <data android:scheme="https" />
+        </intent>
     </queries>
     <application
         android:name=".MyApplication"

BIN
assets/images/bg_update_version.webp


BIN
assets/images/icon_update_app_close.webp


BIN
assets/images/icon_update_new_version_title.webp


+ 5 - 0
lib/data/api/atmob_api.dart

@@ -2,6 +2,7 @@ import 'package:dio/dio.dart';
 import 'package:electronic_assistant/base/app_base_request.dart';
 import 'package:electronic_assistant/base/base_response.dart';
 import 'package:electronic_assistant/data/api/network_module.dart';
+import 'package:electronic_assistant/data/api/request/config_request.dart';
 import 'package:electronic_assistant/data/api/request/agenda_request.dart';
 import 'package:electronic_assistant/data/api/request/agenda_status_request.dart';
 import 'package:electronic_assistant/data/api/request/agenda_todo_request.dart';
@@ -24,6 +25,7 @@ import 'package:electronic_assistant/data/api/response/agenda_list_all_response.
 import 'package:electronic_assistant/data/api/response/agenda_list_mine_response.dart';
 import 'package:electronic_assistant/data/api/response/agenda_response.dart';
 import 'package:electronic_assistant/data/api/response/chat_history_response.dart';
+import 'package:electronic_assistant/data/api/response/config_response.dart';
 import 'package:electronic_assistant/data/api/response/example_info_response.dart';
 import 'package:electronic_assistant/data/api/response/home_info_response.dart';
 import 'package:electronic_assistant/data/api/response/login_response.dart';
@@ -149,6 +151,9 @@ abstract class AtmobApi {
   @POST("/project/secretary/v1/order/status")
   Future<BaseResponse<OrderStatusResponse>> orderStatus(
       @Body() OrderStatusRequest request);
+
+  @POST("/project/secretary/v1/confs")
+  Future<BaseResponse<ConfigResponse>> configs(@Body() ConfigRequest request);
 }
 
 final atmobApi = AtmobApi(defaultDio, baseUrl: Constants.baseUrl);

+ 15 - 0
lib/data/api/request/config_request.dart

@@ -0,0 +1,15 @@
+import 'package:electronic_assistant/base/app_base_request.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'config_request.g.dart';
+
+@JsonSerializable()
+class ConfigRequest extends AppBaseRequest {
+  @JsonKey(name: 'confCodes')
+  List<String> confCodes;
+
+  ConfigRequest(this.confCodes);
+
+  @override
+  Map<String, dynamic> toJson() => _$ConfigRequestToJson(this);
+}

+ 16 - 0
lib/data/api/response/config_response.dart

@@ -0,0 +1,16 @@
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../bean/config_bean.dart';
+
+part 'config_response.g.dart';
+
+@JsonSerializable()
+class ConfigResponse {
+  @JsonKey(name: 'list')
+  List<ConfigBean> list;
+
+  ConfigResponse(this.list);
+
+  factory ConfigResponse.fromJson(Map<String, dynamic> json) =>
+      _$ConfigResponseFromJson(json);
+}

+ 17 - 0
lib/data/bean/config_bean.dart

@@ -0,0 +1,17 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'config_bean.g.dart';
+
+@JsonSerializable()
+class ConfigBean {
+  @JsonKey(name: 'confCode')
+  String confCode;
+
+  @JsonKey(name: 'value')
+  dynamic value;
+
+  ConfigBean(this.confCode, this.value);
+
+  factory ConfigBean.fromJson(Map<String, dynamic> json) =>
+      _$ConfigBeanFromJson(json);
+}

+ 33 - 0
lib/data/bean/version_update_bean.dart

@@ -0,0 +1,33 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'version_update_bean.g.dart';
+
+@JsonSerializable()
+class VersionUpdateBean {
+  @JsonKey(name: 'md5')
+  String? md5;
+
+  @JsonKey(name: 'url')
+  String? url;
+
+  @JsonKey(name: 'desc')
+  String? desc;
+
+  @JsonKey(name: 'extra')
+  String? extra;
+
+  @JsonKey(name: 'force')
+  bool force;
+
+  @JsonKey(name: 'title')
+  String? title;
+
+  @JsonKey(name: 'version')
+  String version;
+
+  VersionUpdateBean(this.version, this.md5, this.url, this.desc, this.extra,
+      this.force, this.title);
+
+  factory VersionUpdateBean.fromJson(Map<String, dynamic> json) =>
+      _$VersionUpdateBeanFromJson(json);
+}

+ 2 - 0
lib/data/consts/constants.dart

@@ -38,6 +38,8 @@ class Constants {
   static bool isProdEnv() {
     return Constants.env == Constants.envProd;
   }
+
+  static const String recordServerVersion = 'recordServerVersion';
 }
 
 String getBaseUrl() {

+ 46 - 0
lib/data/repositories/config_repository.dart

@@ -3,7 +3,12 @@ import 'package:electronic_assistant/data/api/atmob_api.dart';
 import 'package:electronic_assistant/utils/async_util.dart';
 import 'package:electronic_assistant/utils/http_handler.dart';
 
+import '../../utils/app_info_util.dart';
+import '../api/request/config_request.dart';
+import '../api/response/config_response.dart';
 import '../api/response/example_info_response.dart';
+import '../bean/config_bean.dart';
+import '../bean/version_update_bean.dart';
 
 class ConfigRepository {
   ConfigRepository._();
@@ -27,6 +32,47 @@ class ConfigRepository {
       return response;
     });
   }
+
+  Future<ConfigResponse> configs(List<String> confCodes) {
+    return atmobApi
+        .configs(ConfigRequest(confCodes))
+        .then(HttpHandler.handle(true));
+  }
+
+  Future<VersionUpdateBean?> getAppVersion() async {
+    ConfigResponse configResponse = await configs(['app_update']);
+    if (configResponse.list.isEmpty) {
+      return null;
+    }
+    ConfigBean configBean = configResponse.list.first;
+    if (configBean.value is! Map<String, dynamic>) {
+      return null;
+    }
+    VersionUpdateBean bean = VersionUpdateBean.fromJson(configBean.value);
+    String? localVersionName = appInfoUtil.appVersionName;
+    if (bean.version.isEmpty || localVersionName == null) {
+      return null;
+    }
+    if (needsUpdate(bean.version, localVersionName)) {
+      return bean;
+    }
+    return null;
+  }
+
+  bool needsUpdate(String serverVersion, String localVersion) {
+    List<int> serverParts =
+        serverVersion.replaceFirst('v', '').split('.').map(int.parse).toList();
+    List<int> localParts = localVersion.split('.').map(int.parse).toList();
+
+    for (int i = 0; i < serverParts.length; i++) {
+      if (serverParts[i] > localParts[i]) {
+        return true;
+      } else if (serverParts[i] < localParts[i]) {
+        return false;
+      }
+    }
+    return false;
+  }
 }
 
 final configRepository = ConfigRepository._();

+ 181 - 0
lib/dialog/show_update_version_dialog.dart

@@ -0,0 +1,181 @@
+import 'dart:io';
+import 'dart:ui';
+
+import 'package:electronic_assistant/data/bean/version_update_bean.dart';
+import 'package:electronic_assistant/data/consts/constants.dart';
+import 'package:electronic_assistant/resource/assets.gen.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/utils/common_style.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:electronic_assistant/utils/mmkv_util.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+
+import '../utils/launcher_url_util.dart';
+
+class UpdateVersionDialog {
+  static const String tag = 'showUpdateVersionDialog';
+
+  static void dismiss() {
+    SmartDialog.dismiss(tag: tag);
+  }
+
+  static Widget _buildVersionView(String version) {
+    return Container(
+        decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: [
+                '#D0C5FF'.toColor(),
+                ColorName.white,
+                '#647FFF'.toColor()
+              ],
+              begin: Alignment.topLeft,
+              end: Alignment.bottomRight,
+            ),
+            borderRadius: BorderRadius.all(Radius.circular(11.w))),
+        padding: EdgeInsets.all(0.5.w),
+        height: 22.h,
+        child: Container(
+          decoration: getPrimaryBtnDecoration(11.w),
+          padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 2.w),
+          child: Text(
+            version,
+            style: TextStyle(
+                fontSize: 13.sp,
+                color: ColorName.white,
+                fontWeight: FontWeight.bold,
+                height: 0),
+          ),
+        ));
+  }
+
+  static Widget _buildUpdateBtn({VoidCallback? onUpdateClick}) {
+    return GestureDetector(
+      onTap: onUpdateClick,
+      child: Container(
+          width: 148.w,
+          height: 48.w,
+          decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: ['#25262A'.toColor(), '#3F424D'.toColor()],
+              stops: const [0.3, 1.0],
+              begin: Alignment.topLeft,
+              end: Alignment.bottomRight,
+            ),
+            borderRadius: BorderRadius.circular(100.w),
+          ),
+          child: Center(
+            child: Text(
+              '立即更新',
+              style: TextStyle(
+                  fontSize: 14.sp,
+                  color: ColorName.white,
+                  fontWeight: FontWeight.bold),
+            ),
+          )),
+    );
+  }
+
+  static void show(VersionUpdateBean bean) {
+    if (SmartDialog.checkExist(tag: tag)) {
+      return;
+    }
+    SmartDialog.show(
+        tag: tag,
+        backType: SmartBackType.block,
+        clickMaskDismiss: false,
+        maskColor: ColorName.black55,
+        builder: (_) {
+          return SizedBox(
+            width: 304.w,
+            child: IntrinsicHeight(
+              child: Column(
+                children: [
+                  Container(
+                    width: 304.w,
+                    decoration: BoxDecoration(
+                      image: DecorationImage(
+                        image: Assets.images.bgUpdateVersion.provider(),
+                        fit: BoxFit.cover,
+                      ),
+                    ),
+                    child: AspectRatio(
+                        aspectRatio: 960 / 1110,
+                        child: Container(
+                          padding: EdgeInsets.symmetric(horizontal: 42.w),
+                          child: Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
+                            children: [
+                              SizedBox(height: 57.h),
+                              Assets.images.iconUpdateNewVersionTitle
+                                  .image(height: 29.h),
+                              SizedBox(height: 8.h),
+                              _buildVersionView(bean.version),
+                              SizedBox(height: 20.h),
+                              Expanded(
+                                  child:
+                                      _buildUpdateView(bean.title, bean.desc)),
+                              SizedBox(height: 20.h),
+                              _buildUpdateBtn(onUpdateClick: () {
+                                onUpdate(bean.url);
+                              }),
+                              SizedBox(height: 48.h),
+                            ],
+                          ),
+                        )),
+                  ),
+                  SizedBox(height: 12.h),
+                  Visibility(
+                    visible: !bean.force,
+                    child: GestureDetector(
+                        onTap: () {
+                          KVUtil.putString(
+                              Constants.recordServerVersion, bean.version);
+                          SmartDialog.dismiss(tag: tag);
+                        },
+                        child: Assets.images.iconUpdateAppClose
+                            .image(width: 40.w, height: 40.w)),
+                  )
+                ],
+              ),
+            ),
+          );
+        });
+  }
+
+  static Widget _buildUpdateView(String? title, String? desc) {
+    return SingleChildScrollView(
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Text(
+            title ?? '',
+            style: TextStyle(fontSize: 14.sp, color: '#202A4D'.toColor()),
+          ),
+          Visibility(
+              visible: title != null && title.isNotEmpty,
+              child: SizedBox(
+                height: 6.h,
+              )),
+          Text(
+            desc ?? '',
+            style: TextStyle(fontSize: 14.sp, color: '#202A4D'.toColor()),
+          ),
+        ],
+      ),
+    );
+  }
+
+  static void onUpdate(String? url) {
+    if (Platform.isAndroid) {
+      if (url == null) {
+        return;
+      }
+      LauncherUrlUtil.launchHttpUrl(url);
+    } else if (Platform.isIOS) {
+      //TODO ios 跳转苹果商店下载
+    }
+  }
+}

+ 20 - 1
lib/module/main/controller.dart

@@ -2,17 +2,22 @@ import 'dart:ui';
 
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/data/repositories/account_repository.dart';
+import 'package:electronic_assistant/data/repositories/config_repository.dart';
 import 'package:electronic_assistant/module/chat/view.dart';
 import 'package:electronic_assistant/resource/assets.gen.dart';
 import 'package:electronic_assistant/resource/colors.gen.dart';
 import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/utils/error_handler.dart';
 import 'package:electronic_assistant/utils/expand.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 
+import '../../data/consts/constants.dart';
+import '../../dialog/show_update_version_dialog.dart';
 import '../../router/app_pages.dart';
 import '../../utils/desktop_shortcut_utils.dart';
+import '../../utils/mmkv_util.dart';
 import '../login/view.dart';
 
 class MainController extends BaseController {
@@ -41,7 +46,6 @@ class MainController extends BaseController {
 
   DateTime? get lastPressedAt => _lastPressedAt;
 
-
   void changeIndex(int index) {
     _currentIndex.value = index;
   }
@@ -89,6 +93,7 @@ class MainController extends BaseController {
   void onReady() {
     super.onReady();
     _initParameters();
+    checkVersion();
   }
 
   void _initParameters() {
@@ -100,6 +105,20 @@ class MainController extends BaseController {
       Get.toNamed(RoutePath.record);
     }
   }
+
+  void checkVersion() {
+    configRepository.getAppVersion().then((bean) {
+      if (bean == null) {
+        return;
+      }
+      //检查是否已经弹出过更新弹窗
+      if (KVUtil.getString(Constants.recordServerVersion, null) ==
+          bean.version) {
+        return;
+      }
+      UpdateVersionDialog.show(bean);
+    });
+  }
 }
 
 class TabBean {

+ 12 - 1
lib/module/main/drawer/controller.dart

@@ -1,5 +1,6 @@
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/data/consts/event_report_id.dart';
+import 'package:electronic_assistant/data/repositories/config_repository.dart';
 import 'package:electronic_assistant/handler/event_handler.dart';
 import 'package:electronic_assistant/resource/colors.gen.dart';
 import 'package:electronic_assistant/utils/error_handler.dart';
@@ -11,6 +12,7 @@ import 'package:get/get.dart';
 import '../../../data/api/response/user_info_response.dart';
 import '../../../data/repositories/account_repository.dart';
 import '../../../dialog/alert_dialog.dart';
+import '../../../dialog/show_update_version_dialog.dart';
 import '../../../resource/string.gen.dart';
 import '../../../utils/app_info_util.dart';
 import '../controller.dart';
@@ -49,6 +51,15 @@ class MainDrawerController extends BaseController {
 
   void onCheckUpdate() {
     EventHandler.report(EventId.event_105001);
-    ToastUtil.showToast(StringName.versionLatest.tr);
+    configRepository.getAppVersion().then((bean) {
+      if (bean == null) {
+        ToastUtil.showToast(StringName.versionLatest.tr);
+      } else {
+        Get.find<MainController>().closeDrawer();
+        UpdateVersionDialog.show(bean);
+      }
+    }).catchError((error) {
+      ErrorHandler.toastError(error);
+    });
   }
 }

+ 9 - 0
lib/utils/launcher_url_util.dart

@@ -0,0 +1,9 @@
+import 'package:url_launcher/url_launcher.dart';
+
+class LauncherUrlUtil {
+  LauncherUrlUtil._();
+
+  static void launchHttpUrl(String url) {
+    launchUrl(Uri.parse(url));
+  }
+}