diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 747234e..304f87b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -40,4 +40,11 @@
+
+
+
+
+
+
+
diff --git a/lib/data/model/app.dart b/lib/data/model/app.dart
new file mode 100644
index 0000000..7730527
--- /dev/null
+++ b/lib/data/model/app.dart
@@ -0,0 +1,60 @@
+/*
+ app 升级
+*/
+class AppUpdateRequest {
+ String? device;
+ String? channel;
+ String? architecture;
+ String? model;
+
+ AppUpdateRequest({
+ this.device,
+ this.channel,
+ this.architecture,
+ this.model,
+ });
+
+ factory AppUpdateRequest.fromJson(Map json) =>
+ AppUpdateRequest(
+ device: json["device"],
+ channel: json["channel"],
+ architecture: json["architecture"],
+ model: json["model"],
+ );
+
+ Map toJson() => {
+ "device": device,
+ "channel": channel,
+ "architecture": architecture,
+ "model": model,
+ };
+}
+
+class AppUpdateResponse {
+ String? shopUrl;
+ String? fileUrl;
+ String? latestVersion;
+ String? latestDescription;
+
+ AppUpdateResponse({
+ this.shopUrl,
+ this.fileUrl,
+ this.latestVersion,
+ this.latestDescription,
+ });
+
+ factory AppUpdateResponse.fromJson(Map json) =>
+ AppUpdateResponse(
+ shopUrl: json["shopUrl"],
+ fileUrl: json["fileUrl"],
+ latestVersion: json["latestVersion"],
+ latestDescription: json["latestDescription"],
+ );
+
+ Map toJson() => {
+ "shopUrl": shopUrl,
+ "fileUrl": fileUrl,
+ "latestVersion": latestVersion,
+ "latestDescription": latestDescription,
+ };
+}
diff --git a/lib/data/provider/app.dart b/lib/data/provider/app.dart
new file mode 100644
index 0000000..2280162
--- /dev/null
+++ b/lib/data/provider/app.dart
@@ -0,0 +1,22 @@
+import 'package:news_getx/config/storage.dart';
+import 'package:news_getx/data/model/app.dart';
+import 'package:news_getx/data/model/categories.dart';
+import 'package:news_getx/data/model/channels.dart';
+import 'package:news_getx/data/model/news.dart';
+import 'package:news_getx/data/model/tag.dart';
+import 'package:news_getx/utils/http.dart';
+
+/// AppAPI
+class AppUpdateAPI {
+ /// 翻页
+ /// refresh 是否刷新
+ static Future update({
+ required AppUpdateRequest request,
+ }) async {
+ var response = await HttpUtil().post(
+ "/app/update",
+ data: request.toJson(),
+ );
+ return AppUpdateResponse.fromJson(response);
+ }
+}
diff --git a/lib/data/repository/app_repository.dart b/lib/data/repository/app_repository.dart
new file mode 100644
index 0000000..067e346
--- /dev/null
+++ b/lib/data/repository/app_repository.dart
@@ -0,0 +1,8 @@
+import 'package:news_getx/data/model/app.dart';
+import 'package:news_getx/data/provider/app.dart';
+
+class AppRepository {
+ Future update(AppUpdateRequest request) {
+ return AppUpdateAPI.update(request: request);
+ }
+}
diff --git a/lib/data/services/config.dart b/lib/data/services/config.dart
index 921a9a8..e212c8d 100644
--- a/lib/data/services/config.dart
+++ b/lib/data/services/config.dart
@@ -1,16 +1,33 @@
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:news_getx/config/storage.dart';
import 'package:news_getx/data/services/storage.dart';
import 'package:package_info_plus/package_info_plus.dart';
+import 'package:device_info/device_info.dart';
class ConfigService extends GetxService {
static ConfigService get to => Get.find();
bool isFirstOpen = false;
RxBool isGrayFilter = false.obs;
+
PackageInfo? _platform;
+ /// 发布渠道
+ static String channel = "xxx";
+
+ /// 是否 ios
+ static bool isIOS = Platform.isIOS;
+
+ /// android 设备信息
+ static AndroidDeviceInfo? androidDeviceInfo;
+
+ /// ios 设备信息
+ static IosDeviceInfo? iosDeviceInfo;
+
+
String get version => _platform?.version ?? "-";
bool get isRelease => bool.fromEnvironment("dart.vm.product");
@@ -24,9 +41,20 @@ class ConfigService extends GetxService {
];
@override
- void onInit() {
+ void onInit() async {
super.onInit();
isFirstOpen = StorageService.to.getBool(StorageDeviceFirstOpenKey);
+
+ // 加载设备信息
+ getPlatform();
+
+ // 读取设备信息
+ DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
+ if (isIOS) {
+ iosDeviceInfo = await deviceInfoPlugin.iosInfo;
+ } else {
+ androidDeviceInfo = await deviceInfoPlugin.androidInfo;
+ }
}
void changeGrayTheme() {
diff --git a/lib/utils/update.dart b/lib/utils/update.dart
new file mode 100644
index 0000000..3179c6d
--- /dev/null
+++ b/lib/utils/update.dart
@@ -0,0 +1,126 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:install_plugin/install_plugin.dart';
+import 'package:news_getx/data/model/app.dart';
+import 'package:news_getx/data/repository/app_repository.dart';
+import 'package:news_getx/data/services/config.dart';
+import 'package:path_provider/path_provider.dart';
+
+// todo 如何用完一次就消失
+class DownloadController extends GetxController {
+ // 进度条
+ var _progressValue = 0.0.obs;
+
+ double get progressValue => _progressValue.value;
+
+ set progressValue(double value) {
+ _progressValue.value = value;
+ }
+
+ /// 下载文件 & 安装
+ Future _downloadAPKAndSetup(fileUrl) async {
+ _progressValue.value = 0.0;
+
+ // 下载
+ Directory? externalDir = await getExternalStorageDirectory();
+
+ if (externalDir == null) {
+ return;
+ }
+ // 存储路径
+ String fullPath = "${externalDir.path}/release.apk";
+
+ // Dio dio = Dio(BaseOptions(
+ // responseType: ResponseType.bytes,
+ // followRedirects: false,
+ // validateStatus: (status) {
+ // return status! < 500;
+ // }
+ // ));
+ //
+ // // 获取APP文件
+ // Response response = await dio.get(fileUrl);
+ //
+ // // 文件写入
+ // File file = File(fullPath);
+ // var raf = file.openSync(mode: FileMode.write);
+ // raf.writeByteSync(response.data);
+ // await raf.close();
+
+ await Dio().download(fileUrl, fullPath, onReceiveProgress: (count, total) {
+ final value = count / total;
+ if (_progressValue.value != value) {
+ if (_progressValue.value < 1.0) {
+ _progressValue.value = count / total;
+ } else {
+ _progressValue.value = 0.0;
+ }
+ print("${(_progressValue * 100).toStringAsFixed(0)}%");
+ }
+ });
+
+ // 安装
+ await InstallPlugin.installApk(fullPath);
+ }
+}
+
+/// App 更新
+class AppUpdateUtil {
+ static AppUpdateUtil _instance = AppUpdateUtil._internal();
+
+ factory AppUpdateUtil() => _instance;
+
+ AppUpdateUtil._internal();
+
+ AppUpdateResponse? _appUpdateInfo;
+
+ /// 获取更新信息
+ Future run() async {
+ // 提交 设备类型、发行渠道、架构、机型
+ AppUpdateRequest appUpdateRequest = AppUpdateRequest(
+ device: Platform.isIOS ? "ios" : "android",
+ channel: ConfigService.channel,
+ architecture: ConfigService.isIOS
+ ? ConfigService.iosDeviceInfo!.utsname.machine
+ : ConfigService.androidDeviceInfo!.device,
+ model: ConfigService.isIOS
+ ? ConfigService.iosDeviceInfo!.name
+ : ConfigService.androidDeviceInfo!.brand,
+ );
+
+ _appUpdateInfo = await AppRepository().update(appUpdateRequest);
+
+ _runAppUpdate();
+ }
+
+ /// 检查是否有新版
+ Future _runAppUpdate() async {
+ // 比较版本
+ final isNewVersion =
+ _appUpdateInfo!.latestVersion!.compareTo(ConfigService.to.version) > 0;
+
+ // 发现新版本
+ if (isNewVersion) {
+ // todo 确认弹窗 确认完成下载并显示进度条
+ Get.defaultDialog(
+ title: "发现新版本 ${_appUpdateInfo!.latestVersion}",
+ content: GetBuilder(
+ init: DownloadController(),
+ builder: (controller) {
+ return LinearProgressIndicator(value: controller.progressValue);
+ },
+ ),
+ );
+
+ // _appUpdateConformDialog(
+ //
+ // );
+ }
+ }
+
+ /// 升级确认对话框
+ void _appUpdateConformDialog(VoidCallback onPressed) {}
+}
diff --git a/pubspec.lock b/pubspec.lock
index 2542c2b..deee1ac 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -301,6 +301,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.17"
+ install_plugin:
+ dependency: "direct main"
+ description:
+ name: install_plugin
+ sha256: "6fb67ba0781e75de4f2f2266ed25e835bfd277c5bfc2ed034af52774355857c6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.0"
intl:
dependency: "direct main"
description:
@@ -457,10 +465,26 @@ packages:
dependency: "direct main"
description:
name: permission_handler
- sha256: e968207ce71d8b40d719aeca3e5a8b684494ecbe9a577dd67cc701216bcccf0a
+ sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "7.2.0"
+ version: "10.4.3"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ sha256: "37cedf9e5d530582bc813f63ed278e4a9dc42bb0948b68316e253cb0cd56a4b1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "10.3.1"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ sha256: "7a187b671a39919462af2b5e813148365b71a615979165a119868d667fe90c03"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "9.1.3"
permission_handler_platform_interface:
dependency: transitive
description:
@@ -469,6 +493,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.11.1"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.1.3"
petitparser:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e418899..a67da04 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -86,7 +86,7 @@ dependencies:
path_provider: ^2.0.1
# permission 权限
- permission_handler: ^7.1.0
+ permission_handler: ^10.4.3
# 错误收集
sentry: ^5.0.0
@@ -97,6 +97,9 @@ dependencies:
# 通过 scheme 打开APP 获取 参数
uni_links: ^0.5.1
+ # app 更新
+ install_plugin: ^2.1.0
+
dev_dependencies:
flutter_test:
sdk: flutter