diff --git a/lib/common/manager/app_manager.dart b/lib/common/manager/app_manager.dart index 87f5ea7..a865297 100644 --- a/lib/common/manager/app_manager.dart +++ b/lib/common/manager/app_manager.dart @@ -1,8 +1,12 @@ +import 'package:cashier_reserve/common/print/print.dart'; import 'package:cashier_reserve/common/push/push.dart'; import 'package:cashier_reserve/data_model/login/login_result.dart'; import 'package:cashier_reserve/login/login_view.dart'; import 'package:cashier_reserve/model/reserve_model.dart'; +import 'package:cashier_reserve/model/version_model.dart'; +import 'package:cashier_reserve/update_version/update_version_view.dart'; import 'package:easy_refresh/easy_refresh.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import '../base/ui.dart'; import '../channel/channel_event.dart'; @@ -62,6 +66,36 @@ class AppManager { YJPush.presentWidget(globalContext!, const LoginView()); } + static void checkAppVersion() async { + final res = await VersionModel.requestNewVersionInfo(); + yjPrint(res); + + if (res == null || res.version == null) { + return; + } + + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + yjPrint("version == ${packageInfo.version}"); + + List serverList = res.version!.split("."); + List localList = packageInfo.version.split("."); + + if (serverList.length != 3 || localList.length != 3) { + return; + } + + int serverVersion = int.parse(serverList[0]) * 10000 + int.parse(serverList[1]) * 100 + int.parse(serverList[2]); + int localVersion = int.parse(localList[0]) * 10000 + int.parse(localList[1]) * 100 + int.parse(localList[2]); + + if (serverVersion <= localVersion) { + return; + } + + yjPrint("serverVersion == $serverVersion, localVersion == $localVersion"); + + YJPush.presentWidget(globalContext!, UpdateVersionView(versionModel: res,)); + } + static void disposeLoginWidget() { _isAlertLogin = false; } diff --git a/lib/data_model/version/update_version_model.dart b/lib/data_model/version/update_version_model.dart new file mode 100644 index 0000000..cd4491a --- /dev/null +++ b/lib/data_model/version/update_version_model.dart @@ -0,0 +1,128 @@ +import 'dart:convert'; + +/// createdAt : 1712455187626 +/// id : 19 +/// isUp : 0 +/// message : "需要更新" +/// sel : 1 +/// source : "PC" +/// type : "android" +/// updatedAt : 1725353572331 +/// url : "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/version/1.4.20.exe" +/// version : "1.4.21" + +UpdateVersionModel updateVersionModelFromJson(String str) => + UpdateVersionModel.fromJson(json.decode(str)); + +String updateVersionModelToJson(UpdateVersionModel data) => + json.encode(data.toJson()); + +class UpdateVersionModel { + UpdateVersionModel({ + num? createdAt, + num? id, + num? isUp, + String? message, + num? sel, + String? source, + String? type, + num? updatedAt, + String? url, + String? version, + }) { + _createdAt = createdAt; + _id = id; + _isUp = isUp; + _message = message; + _sel = sel; + _source = source; + _type = type; + _updatedAt = updatedAt; + _url = url; + _version = version; + } + + UpdateVersionModel.fromJson(dynamic json) { + _createdAt = json['createdAt']; + _id = json['id']; + _isUp = json['isUp']; + _message = json['message']; + _sel = json['sel']; + _source = json['source']; + _type = json['type']; + _updatedAt = json['updatedAt']; + _url = json['url']; + _version = json['version']; + } + + num? _createdAt; + num? _id; + num? _isUp; + String? _message; + num? _sel; + String? _source; + String? _type; + num? _updatedAt; + String? _url; + String? _version; + + UpdateVersionModel copyWith({ + num? createdAt, + num? id, + num? isUp, + String? message, + num? sel, + String? source, + String? type, + num? updatedAt, + String? url, + String? version, + }) => + UpdateVersionModel( + createdAt: createdAt ?? _createdAt, + id: id ?? _id, + isUp: isUp ?? _isUp, + message: message ?? _message, + sel: sel ?? _sel, + source: source ?? _source, + type: type ?? _type, + updatedAt: updatedAt ?? _updatedAt, + url: url ?? _url, + version: version ?? _version, + ); + + num? get createdAt => _createdAt; + + num? get id => _id; + + num? get isUp => _isUp; + + String? get message => _message; + + num? get sel => _sel; + + String? get source => _source; + + String? get type => _type; + + num? get updatedAt => _updatedAt; + + String? get url => _url; + + String? get version => _version; + + Map toJson() { + final map = {}; + map['createdAt'] = _createdAt; + map['id'] = _id; + map['isUp'] = _isUp; + map['message'] = _message; + map['sel'] = _sel; + map['source'] = _source; + map['type'] = _type; + map['updatedAt'] = _updatedAt; + map['url'] = _url; + map['version'] = _version; + return map; + } +} diff --git a/lib/home/home_view_model.dart b/lib/home/home_view_model.dart index 3b00e69..ac3941b 100644 --- a/lib/home/home_view_model.dart +++ b/lib/home/home_view_model.dart @@ -49,6 +49,8 @@ class HomeViewModel extends BaseUIModel { Future.delayed(const Duration(milliseconds: 700), () { _checkLogin(); + + _checkAppVersion(); }); EventManager.addListener(this, (event) { @@ -88,6 +90,10 @@ class HomeViewModel extends BaseUIModel { yjPrint("is login $flag"); } + _checkAppVersion() { + AppManager.checkAppVersion(); + } + void setIndex(int index) { if (_currentIndex == index) { return; diff --git a/lib/home/order_view.dart b/lib/home/order_view.dart index c9f05b8..a6b0bb4 100644 --- a/lib/home/order_view.dart +++ b/lib/home/order_view.dart @@ -7,10 +7,8 @@ import '../common/base/provider.dart'; class OrderView extends BaseUI { @override Widget buildBody(BuildContext context) { - return Container( - child: Center( - child: Text("Order"), - ), + return const Center( + child: Text("Order"), ); } diff --git a/lib/login/login_view.dart b/lib/login/login_view.dart index bec07da..deb13e8 100644 --- a/lib/login/login_view.dart +++ b/lib/login/login_view.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:cashier_reserve/common/encrypt/pwd.dart'; import 'package:cashier_reserve/common/manager/app_manager.dart'; diff --git a/lib/model/version_model.dart b/lib/model/version_model.dart new file mode 100644 index 0000000..7668958 --- /dev/null +++ b/lib/model/version_model.dart @@ -0,0 +1,12 @@ +import 'package:cashier_reserve/common/request/request_manager.dart'; +import 'package:cashier_reserve/data_model/version/update_version_model.dart'; + +class VersionModel { + static Future requestNewVersionInfo() async { + final r = await RequestManager.get("/api/tbVersion/findBySource", params: {"source": "电话机"}); + if (r is Map) { + return UpdateVersionModel.fromJson(r); + } + return null; + } +} \ No newline at end of file diff --git a/lib/update_version/update_version_view.dart b/lib/update_version/update_version_view.dart new file mode 100644 index 0000000..909c623 --- /dev/null +++ b/lib/update_version/update_version_view.dart @@ -0,0 +1,217 @@ + +import 'package:cashier_reserve/common/print/print.dart'; +import 'package:cashier_reserve/data_model/version/update_version_model.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:percent_indicator/percent_indicator.dart'; + +class UpdateVersionView extends StatefulWidget { + final UpdateVersionModel versionModel; + + const UpdateVersionView({super.key, required this.versionModel}); + + @override + State createState() { + return _UpdateVersionViewState(); + } +} + +class _UpdateVersionViewState extends State { + DateTime? lastPopTime; + + double progress = 0; + String progressText = "0%"; + + bool isDownload = false; + + // 用于控制下载任务取消的CancelToken + CancelToken? _cancelToken; + + @override + void dispose() { + _cancelToken?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: (widget.versionModel.isUp ?? 0) != 0, + onPopInvokedWithResult: (didPop, result) async { + // 点击返回键的操作 + if (lastPopTime == null || + DateTime.now().difference(lastPopTime!) > + const Duration(seconds: 2)) { + lastPopTime = DateTime.now(); + // Utils.toast('再按一次退出', context); + } else { + lastPopTime = DateTime.now(); + // 退出app + await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } + }, + child: Scaffold( + backgroundColor: Colors.black.withOpacity(0.2), + body: GestureDetector( + onTap: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + child: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + color: const Color(0x55000000), + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB(30, 30, 30, 30), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + width: 400, + // height: 400, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "发现新版本", + style: + TextStyle(fontSize: 22, color: Color(0xff333333)), + ), + const SizedBox( + height: 20, + ), + Text( + widget.versionModel.message ?? "", + style: const TextStyle( + fontSize: 16, color: Color(0xff666666)), + ), + const SizedBox( + height: 20, + ), + isDownload + ? LinearPercentIndicator( + padding: EdgeInsets.zero, + animation: false, + lineHeight: 20.0, + // animationDuration: 2500, + percent: progress, + center: Text(progressText), + // linearStrokeCap: LinearStrokeCap.roundAll, + progressColor: Colors.green, + ) + : Container(), + isDownload + ? const SizedBox( + height: 20, + ) + : Container(), + _buildActionViews(context), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildActionViews(BuildContext context) { + bool isMustUpdate = widget.versionModel.isUp == 1; + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + isMustUpdate + ? Container() + : ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.white), + foregroundColor: + WidgetStateProperty.all(const Color(0xff666666)), + minimumSize: WidgetStateProperty.all(const Size(100, 40)), + shape: WidgetStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + )), + ), + child: const Text("跳过"), + ), + isMustUpdate + ? Container() + : const SizedBox( + width: 20, + ), + ElevatedButton( + onPressed: () { + startDownload(); + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.blue), + foregroundColor: WidgetStateProperty.all(Colors.white), + minimumSize: + WidgetStateProperty.all(Size(isMustUpdate ? 200 : 100, 40)), + shape: WidgetStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + )), + ), + child: const Text("立即更新"), + ), + ]); + } + + startDownload() async { + setState(() { + isDownload = true; + }); + + String fileUrl = widget.versionModel.url ?? ""; + if (fileUrl.isEmpty) { + yjPrint("下载地址为空"); + return; + } + + _cancelToken?.cancel(); + + setState(() { + progress = 0.0; + progressText = "0.0%"; + }); + + yjPrint("fileUrl: $fileUrl"); + + Dio dio = Dio(); + try { + String savePath = await getPhoneLocalPath(); + String appName = "file.apk"; + _cancelToken = CancelToken(); // 创建CancelToken对象 + await dio.download( + fileUrl, "$savePath$appName", onReceiveProgress: (received, total) { + if (total != -1) { + ///当前下载的百分比例 + double currentProgress = received / total; + setState(() { + progressText = "${(currentProgress * 100).toStringAsFixed(2)}%"; + progress = double.parse(currentProgress.toStringAsFixed(2)); + }); + yjPrint(progress); + + _cancelToken = null; + } + }, cancelToken: _cancelToken); + + yjPrint("文件下载成功"); + } catch (e) { + yjPrint("文件下载失败:$e"); + } + } + + Future getPhoneLocalPath() async { + final directory = await getExternalStorageDirectory(); + return directory?.path ?? ''; + } +} diff --git a/pubspec.lock b/pubspec.lock index 99b64aa..2a9d08d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -418,6 +418,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.1.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" path: dependency: transitive description: @@ -783,6 +799,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3c9fe0a..1c36330 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: pointycastle: ^3.9.1 url_launcher: ^6.3.1 easy_refresh: ^3.4.0 + package_info_plus: ^8.1.1 dev_dependencies: flutter_test: