From c7f4c04f69827971ac2bafe9c16a96299954efe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8F=98=E8=8F=98?= Date: Sun, 12 Sep 2021 22:32:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=9A=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=AA=A2=E6=B8=AC=E3=80=81=E4=B8=8B=E8=BC=89=E6=96=B0?= =?UTF-8?q?=E7=89=88=E6=9C=AC(=E8=87=AA=E5=8B=95=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=B0=9A=E6=9C=AA=E5=AF=A6=E8=A3=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/Build.yml | 3 - lang/zh_tw.json | 2 + lib/Screen/Settings.dart | 71 +++++++---- lib/Utility/Config.dart | 1 + lib/Utility/Updater.dart | 227 ++++++++++++++++++++++++++++++++++++ lib/main.dart | 71 +++++++++++ pubspec.lock | 21 ++++ pubspec.yaml | 2 + 8 files changed, 372 insertions(+), 26 deletions(-) create mode 100644 lib/Utility/Updater.dart diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 33cb5871..67b3e78b 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -4,9 +4,6 @@ on: push: branches: - main - pull_request: - branches: - - main workflow_dispatch: diff --git a/lang/zh_tw.json b/lang/zh_tw.json index 70815756..ac483559 100644 --- a/lang/zh_tw.json +++ b/lang/zh_tw.json @@ -88,6 +88,8 @@ "settings.advanced.title": "進階設定", "settings.advanced.assets.check": "檢查資源檔案完整性", "settings.advanced.max.log": "遊戲日誌紀錄最大行數", + "settings.advanced.channel.stable": "穩定版更新通道", + "settings.advanced.channel.dev": "開發版更新通道", "edit.instance.title": "編輯安裝檔", "edit.instance.homepage.instance.name": "安裝檔名稱: ", "edit.instance.homepage.instance.enter": "請輸入安裝檔名稱", diff --git a/lib/Screen/Settings.dart b/lib/Screen/Settings.dart index 53eb54e0..6bca7cf6 100644 --- a/lib/Screen/Settings.dart +++ b/lib/Screen/Settings.dart @@ -4,6 +4,7 @@ import 'package:rpmlauncher/Launcher/GameRepository.dart'; import 'package:rpmlauncher/Model/JvmArgs.dart'; import 'package:rpmlauncher/Utility/Config.dart'; import 'package:rpmlauncher/Utility/Theme.dart'; +import 'package:rpmlauncher/Utility/Updater.dart'; import 'package:rpmlauncher/Utility/i18n.dart'; import 'package:rpmlauncher/Utility/utility.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; @@ -224,29 +225,26 @@ class SettingScreen_ extends State { i18n.Format("settings.appearance.theme"), style: title_, ), - Center( - child: DropdownButton( - value: ThemeValue, - items: [ - DropdownMenuItem( - value: ThemeUtility.Light, - child: - Text(ThemeUtility.toI18nString(ThemeUtility.Light)), - ), - DropdownMenuItem( - value: ThemeUtility.Dark, - child: - Text(ThemeUtility.toI18nString(ThemeUtility.Dark)), - ), - ], - onChanged: (dynamic themeId) async { - await DynamicTheme.of(context)!.setTheme(themeId); - setState(() { - ThemeValue = themeId; - Config.Change('theme_id', themeId); - }); - }), - ), + DropdownButton( + value: ThemeValue, + items: [ + DropdownMenuItem( + value: ThemeUtility.Light, + child: + Text(ThemeUtility.toI18nString(ThemeUtility.Light)), + ), + DropdownMenuItem( + value: ThemeUtility.Dark, + child: Text(ThemeUtility.toI18nString(ThemeUtility.Dark)), + ), + ], + onChanged: (dynamic themeId) async { + await DynamicTheme.of(context)!.setTheme(themeId); + setState(() { + ThemeValue = themeId; + Config.Change('theme_id', themeId); + }); + }), Text( i18n.Format("settings.appearance.window.size.title"), style: title_, @@ -366,6 +364,31 @@ class SettingScreen_ extends State { Config.Change("auto_dependencies", AutoDependencies); }); }), + Text("RPMLauncher 更新通道", style: title_, textAlign: TextAlign.center), + Center( + child: DropdownButton( + value: UpdateChannel, + items: [ + DropdownMenuItem( + value: UpdateChannels.stable, + child: Text(Updater.toI18nString(UpdateChannels.stable)), + ), + DropdownMenuItem( + value: UpdateChannels.dev, + child: Text(Updater.toI18nString(UpdateChannels.dev)), + ), + ], + onChanged: (dynamic Channel) async { + setState(() { + UpdateChannel = Channel; + Config.Change( + 'update_channel', Updater.toStringFromChannelType(Channel)); + }); + }), + ), + SizedBox( + height: 12, + ), Row( children: [ SizedBox( @@ -535,6 +558,8 @@ class SettingScreen_ extends State { } int ThemeValue = Config.GetValue('theme_id'); +UpdateChannels UpdateChannel = + Updater.getChannelFromString(Config.GetValue('update_channel')); class SettingScreen extends StatefulWidget { @override diff --git a/lib/Utility/Config.dart b/lib/Utility/Config.dart index 1fbd9e2e..6789bedc 100644 --- a/lib/Utility/Config.dart +++ b/lib/Utility/Config.dart @@ -24,6 +24,7 @@ class Config { "show_log": false, "auto_dependencies": true, "theme_id": 0, + "update_channel": "stable" }; static void Change(String key, value) { diff --git a/lib/Utility/Updater.dart b/lib/Utility/Updater.dart new file mode 100644 index 00000000..2ccc7e90 --- /dev/null +++ b/lib/Utility/Updater.dart @@ -0,0 +1,227 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:archive/archive.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:http/http.dart'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart'; +import 'package:rpmlauncher/LauncherInfo.dart'; +import 'package:rpmlauncher/Utility/i18n.dart'; +import 'package:rpmlauncher/path.dart'; + +enum UpdateChannels { stable, dev } + +class Updater { + static final String _updateUrl = + "https://raw.githubusercontent.com/RPMTW/RPMTW-website-data/main/data/RPMLauncher/update.json"; + + static String toI18nString(UpdateChannels channel) { + switch (channel) { + case UpdateChannels.stable: + return i18n.Format('settings.advanced.channel.stable'); + case UpdateChannels.dev: + return i18n.Format('settings.advanced.channel.dev'); + default: + return "stable"; + } + } + + static String toStringFromChannelType(UpdateChannels channel) { + switch (channel) { + case UpdateChannels.stable: + return "stable"; + case UpdateChannels.dev: + return "dev"; + default: + return "stable"; + } + } + + static UpdateChannels getChannelFromString(String channel) { + switch (channel) { + case "stable": + return UpdateChannels.stable; + case "dev": + return UpdateChannels.dev; + default: + return UpdateChannels.stable; + } + } + + static bool isStable(UpdateChannels channel) { + return channel == UpdateChannels.stable; + } + + static bool isDev(UpdateChannels channel) { + return channel == UpdateChannels.dev; + } + + static bool versionCompareTo(String a, String b) { + int aInt = int.parse(a.split(".").join("")); + int bInt = int.parse(b.split(".").join("")); + return (aInt > bInt) || (aInt == bInt); + } + + static bool versionCodeCompareTo(String a, int b) { + return int.parse(a) > b; + } + + static Future checkForUpdate(UpdateChannels channel) async { + http.Response response = await http.get(Uri.parse(_updateUrl)); + Map data = json.decode(response.body); + Map VersionList = data['version_list']; + + bool needUpdate(Map data) { + String latestVersion = data['latest_version']; + String latestVersionCode = data['latest_version_code']; + bool mainVersionCheck = + versionCompareTo(latestVersion, LauncherInfo.getVersion()); + + bool versionCodeCheck = versionCodeCompareTo( + latestVersionCode, LauncherInfo.getVersionCode()); + + bool needUpdate = + mainVersionCheck || (mainVersionCheck && versionCodeCheck); + + return needUpdate; + } + + VersionInfo getVersionInfo(Map data) { + String latestVersion = data['latest_version']; + String latestVersionCode = data['latest_version_code']; + return VersionInfo.fromJson(VersionList[latestVersion][latestVersionCode], + latestVersionCode, latestVersion, VersionList, needUpdate(data)); + } + + if (isStable(channel)) { + Map stable = data['stable']; + return getVersionInfo(stable); + } else if (isDev(channel)) { + Map dev = data['dev']; + return getVersionInfo(dev); + } else { + return VersionInfo(needUpdate: false); + } + } + + static Future update(VersionInfo info) async { + Directory updateDir = Directory(join(dataHome.absolute.path, "update")); + + String operatingSystem = Platform.operatingSystem; + String downloadUrl; + + switch (operatingSystem) { + case "linux": + downloadUrl = info.downloadUrl!.linux; + break; + case "windows": + String OSVersion = Platform.operatingSystemVersion; + if (OSVersion.contains('10') || OSVersion.contains('11')) { + //Windows 10/11 + downloadUrl = info.downloadUrl!.windows_10_11; + } else if (OSVersion.contains('7')) { + //Windows 7 + downloadUrl = info.downloadUrl!.windows_7; + } else { + throw Exception("Unsupported OS"); + } + break; + case "macos": + downloadUrl = info.downloadUrl!.macos; + break; + default: + throw Exception("Unknown operating system"); + } + Dio dio = Dio(); + File updateFile = File(join(updateDir.absolute.path, "update.zip")); + await dio.download(downloadUrl, updateFile.absolute.path, + onReceiveProgress: (count, total) { + print((count / total * 100).toStringAsFixed(2) + "%"); + }); + Archive archive = ZipDecoder().decodeBytes(updateFile.readAsBytesSync()); + + for (ArchiveFile file in archive) { + if (file.isFile) { + File(join(updateDir.absolute.path, "unziped", file.name)) + ..createSync(recursive: true) + ..writeAsBytesSync(file.content as List); + } else { + Directory(join(updateDir.absolute.path, "unziped", file.name)) + ..createSync(recursive: true); + } + } + } +} + +class VersionInfo { + final DownloadUrl? downloadUrl; + final UpdateChannels? type; + final String? changelog; + final List? changelogWidgets; + final String? versionCode; + final String? version; + final bool needUpdate; + + const VersionInfo({ + this.downloadUrl, + this.type, + this.versionCode, + this.version, + this.changelog, + this.changelogWidgets, + required this.needUpdate, + }); + factory VersionInfo.fromJson(Map json, String version_code, String version, + Map VersionList, bool needUpdate) { + List changelogs = []; + List _changelogWidgets = []; + VersionList.keys.forEach((_version) { + VersionList[_version].keys.forEach((_versionCode) { + bool mainVersionCheck = Updater.versionCompareTo(_version, version); + bool versionCodeCheck = + Updater.versionCodeCompareTo(_versionCode, int.parse(version_code)); + if (mainVersionCheck || (mainVersionCheck && versionCodeCheck)) { + String _changelog = VersionList[_version][_versionCode]['changelog']; + changelogs.add("\\- " + _changelog); + } + }); + }); + + return VersionInfo( + downloadUrl: DownloadUrl.fromJson(json['download_url']), + changelog: changelogs.join(" \n"), + type: Updater.getChannelFromString(json['type']), + versionCode: version_code, + version: version, + needUpdate: needUpdate, + changelogWidgets: _changelogWidgets); + } + + Map toJson() => { + 'download_url': downloadUrl, + 'type': type, + }; +} + +class DownloadUrl { + final String windows_10_11; + final String windows_7; + final String linux; + final String macos; + + const DownloadUrl({ + required this.windows_10_11, + required this.windows_7, + required this.linux, + required this.macos, + }); + factory DownloadUrl.fromJson(Map json) => DownloadUrl( + windows_10_11: json['windows_10_11'], + windows_7: json['windows_7'], + linux: json['linux'], + macos: json['macos'], + ); +} diff --git a/lib/main.dart b/lib/main.dart index 3b994e4c..e993101b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,9 +2,11 @@ import 'dart:convert'; import 'dart:io'; import 'dart:ui'; +import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:rpmlauncher/Account/Account.dart'; import 'package:rpmlauncher/Screen/Edit.dart'; import 'package:rpmlauncher/Screen/MojangAccount.dart'; +import 'package:rpmlauncher/Utility/Updater.dart'; import 'package:rpmlauncher/Widget/CheckDialog.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/material.dart'; @@ -152,7 +154,76 @@ class _HomePageState extends State { ]); })); }); + } else { + UpdateChannels UpdateChannel = + Updater.getChannelFromString(Config.GetValue('update_channel')); + + Updater.checkForUpdate(UpdateChannel).then((VersionInfo info) { + if (info.needUpdate == true) { + Future.delayed(Duration.zero, () { + TextStyle _title = TextStyle(fontSize: 20); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + StatefulBuilder(builder: (context, setState) { + return AlertDialog( + title: Text("更新 RPMLauncher", + textAlign: TextAlign.center), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "偵測到您的 RPMLauncher 版本過舊,您是否需要更新,我們建議您更新以獲得更佳體驗\n", + style: TextStyle(fontSize: 18), + ), + Text( + "最新版本: ${info.version}.${info.versionCode}", + style: _title, + ), + Text( + "目前版本: ${LauncherInfo.getVersion()}.${LauncherInfo.getVersionCode()}", + style: _title, + ), + Text( + "變更日誌: ", + style: _title, + ), + Container( + width: MediaQuery.of(context).size.width / 2, + height: + MediaQuery.of(context).size.height / 3, + child: Markdown( + selectable: true, + styleSheet: MarkdownStyleSheet( + textAlign: WrapAlignment.center, + h1Align: WrapAlignment.center, + unorderedListAlign: WrapAlignment.center, + ), + data: info.changelog.toString(), + ), + ) + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text("不要更新")), + TextButton( + onPressed: () { + // Navigator.pop(context); + Updater.update(info); + }, + child: Text("更新")) + ]); + })); + }); + } + }); } + isInit = true; } diff --git a/pubspec.lock b/pubspec.lock index 7bf46631..9f54e1ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.0" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" dynamic_themes: dependency: "direct main" description: @@ -174,6 +181,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.6" flutter_test: dependency: "direct dev" description: flutter @@ -240,6 +254,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 96ddc4b1..8794413a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,8 @@ dependencies: auto_size_text: 3.0.0-nullsafety.0 toml: ^0.11.0 line_icons: ^2.0.1 + flutter_markdown: ^0.6.6 + dio: ^4.0.0 dev_dependencies: flutter_test: