From ed8aa0d57dcb38fa2fb6db943ef0941540d31b0f Mon Sep 17 00:00:00 2001 From: niuhuan Date: Thu, 11 Nov 2021 17:05:48 +0800 Subject: [PATCH] auto upgrade --- android/app/src/main/AndroidManifest.xml | 2 +- go/mobile/bind-android-debug.sh | 2 +- go/mobile/bind-android.sh | 2 +- lib/basic/Common.dart | 10 +- lib/basic/config/Version.dart | 142 ++++++++++++++++-- lib/screens/AboutScreen.dart | 140 ++++++++++++++++- lib/screens/AppScreen.dart | 42 ++++-- lib/screens/InitScreen.dart | 2 +- lib/screens/SettingsScreen.dart | 5 +- lib/screens/SpaceScreen.dart | 19 ++- lib/screens/components/Badge.dart | 42 ++++++ .../xcshareddata/xcschemes/Runner.xcscheme | 14 +- 12 files changed, 372 insertions(+), 50 deletions(-) create mode 100644 lib/screens/components/Badge.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6a1054c..56e4a12 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ chooseListDialog( {String? tips}) async { List widgets = []; if (tips != null) { - widgets.add( - Container( - padding: EdgeInsets.fromLTRB(15, 5, 15, 15), - child: Text(tips), - ) - ); + widgets.add(Container( + padding: EdgeInsets.fromLTRB(15, 5, 15, 15), + child: Text(tips), + )); } widgets.addAll(items.map((e) => SimpleDialogOption( onPressed: () { diff --git a/lib/basic/config/Version.dart b/lib/basic/config/Version.dart index 4042c05..96f48ee 100644 --- a/lib/basic/config/Version.dart +++ b/lib/basic/config/Version.dart @@ -1,35 +1,155 @@ import 'dart:async' show Future; +import 'dart:convert'; +import 'package:event/event.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; +import 'package:pikapika/basic/Common.dart'; import '../Method.dart'; const _versionUrl = - "https://api.github.com/repos/niuhuan/pikapi-flutter/releases/latest"; + "https://api.github.com/repos/niuhuan/pikapika/releases/latest"; const _versionAssets = 'lib/assets/version.txt'; RegExp _versionExp = RegExp(r"^v\d+\.\d+.\d+$"); late String _version; -var _latestVersion = ""; +String? _latestVersion; + +const _propertyName = "checkVersionPeriod"; +late int _period = -1; Future initVersion() async { + // 当前版本 try { _version = (await rootBundle.loadString(_versionAssets)).trim(); } catch (e) { _version = "dirty"; } + // 检查周期 + _period = int.parse(await method.loadProperty(_propertyName, "0")); + if (_period > 0) { + if (DateTime.now().millisecondsSinceEpoch > _period) { + await method.saveProperty(_propertyName, "0"); + _period = 0; + } + } } -Future autoCheckNewVersion() async {} +var versionEvent = Event(); +String currentVersion() { + return _version; +} + +String? latestVersion() { + return _latestVersion; +} + +Future autoCheckNewVersion() { + if (_period != 0) { + // -1 不检查, >0 未到检查时间 + return Future.value(); + } + return _versionCheck(); +} + +Future manualCheckNewVersion(BuildContext context) async { + try { + defaultToast(context, "检查更新中"); + await _versionCheck(); + defaultToast(context, "检查更新成功"); + } catch (e) { + defaultToast(context, "检查更新失败 : $e"); + } +} + +bool dirtyVersion() { + return !_versionExp.hasMatch(_version); +} + +// maybe exception Future _versionCheck() async { if (_versionExp.hasMatch(_version)) { - // exception - String latestVersion = (await method.httpGet(_versionUrl)).trim(); - if (latestVersion != _version) { - // new Version + var json = jsonDecode(await method.httpGet(_versionUrl)); + if (json["name"] != null) { + String latestVersion = (json["name"]); + if (latestVersion != _version) { + _latestVersion = latestVersion; + } } - } else { - // dirtyVersion - } - // + } // else dirtyVersion + versionEvent.broadcast(); +} + +String _periodText() { + if (_period < 0) { + return "自动检查更新已关闭"; + } + if (_period == 0) { + return "自动检查更新已开启"; + } + return "下次检查时间 : " + + formatDateTimeToDateTime( + DateTime.fromMillisecondsSinceEpoch(_period), + ); +} + +Future _choosePeriod(BuildContext context) async { + var result = await chooseListDialog( + context, + "自动检查更新", + ["开启", "一周后", "一个月后", "一年后", "关闭"], + tips: "重启后红点会消失", + ); + switch (result) { + case "开启": + await method.saveProperty(_propertyName, "0"); + _period = 0; + break; + case "一周后": + var time = DateTime.now().millisecondsSinceEpoch + (1000 * 3600 * 24 * 7); + await method.saveProperty(_propertyName, "$time"); + _period = time; + break; + case "一个月后": + var time = + DateTime.now().millisecondsSinceEpoch + (1000 * 3600 * 24 * 30); + await method.saveProperty(_propertyName, "$time"); + _period = time; + break; + case "一年后": + var time = + DateTime.now().millisecondsSinceEpoch + (1000 * 3600 * 24 * 365); + await method.saveProperty(_propertyName, "$time"); + _period = time; + break; + case "关闭": + await method.saveProperty(_propertyName, "-1"); + _period = -1; + break; + } +} + +Widget autoUpdateCheckSetting() { + return StatefulBuilder( + builder: (BuildContext context, void Function(void Function()) setState) { + return ListTile( + title: Text("自动检查更新"), + subtitle: Text(_periodText()), + onTap: () async { + await _choosePeriod(context); + setState(() {}); + }, + ); + }, + ); +} + +String formatDateTimeToDateTime(DateTime c) { + try { + return "${add0(c.year, 4)}-${add0(c.month, 2)}-${add0(c.day, 2)} ${add0(c.hour, 2)}:${add0(c.minute, 2)}"; + } catch (e) { + return "-"; + } } diff --git a/lib/screens/AboutScreen.dart b/lib/screens/AboutScreen.dart index 9b99e85..6bf0396 100644 --- a/lib/screens/AboutScreen.dart +++ b/lib/screens/AboutScreen.dart @@ -1,13 +1,42 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:pikapika/basic/Cross.dart'; +import 'package:pikapika/basic/config/Version.dart'; +import 'package:pikapika/screens/components/Badge.dart'; + +const _releasesUrl = "https://github.com/niuhuan/pikapika/releases"; // 关于 -class AboutScreen extends StatelessWidget { +class AboutScreen extends StatefulWidget { + @override + State createState() => _AboutScreenState(); +} + +class _AboutScreenState extends State { + @override + void initState() { + versionEvent.subscribe(_onVersion); + super.initState(); + } + + @override + void dispose() { + versionEvent.unsubscribe(_onVersion); + super.dispose(); + } + + void _onVersion(dynamic a) { + setState(() {}); + } + @override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; var min = size.width < size.height ? size.width : size.height; - + var _currentVersion = currentVersion(); + var _latestVersion = latestVersion(); + var _dirty = dirtyVersion(); return Scaffold( appBar: AppBar( title: Text('关于'), @@ -26,15 +55,37 @@ class AboutScreen extends StatelessWidget { ), ), ), + Container(height: 20), + Divider(), Container( - padding: EdgeInsets.all(20), - child: Text( - '请从软件取得渠道获取更新\n本软件开源, 若您想提出改进建议或者获取源码, 请在开源社区搜索 pikapi', - style: TextStyle( - height: 1.3, - ), + padding: EdgeInsets.only(left: 20, right: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '软件版本 : $_currentVersion', + style: TextStyle( + height: 1.3, + ), + ), + Row( + children: [ + Text( + "检查更新 : ", + style: TextStyle( + height: 1.3, + ), + ), + _dirty ? _buildDirty() : _buildNewVersion(_latestVersion), + Expanded(child: Container()), + ], + ), + ], ), ), + Divider(), + autoUpdateCheckSetting(), + Divider(), Container( padding: EdgeInsets.all(20), child: SelectableText( @@ -49,8 +100,81 @@ class AboutScreen extends StatelessWidget { ), ), ), + Divider(), ], ), ); } + + _buildNewVersion(String? latestVersion) { + if (latestVersion != null) { + return Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Badged( + child: Container( + padding: EdgeInsets.only(right: 12), + child: Text( + latestVersion, + style: TextStyle(height: 1.3), + ), + ), + badge: "1", + ), + ), + TextSpan(text: " "), + TextSpan( + text: "去下载", + style: TextStyle( + height: 1.3, + color: Theme.of(context).colorScheme.primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () => openUrl(_releasesUrl), + ), + ], + ), + ); + } + return Text.rich( + TextSpan( + children: [ + TextSpan(text: "未检测到新版本", style: TextStyle(height: 1.3)), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Container( + padding: EdgeInsets.all(4), + margin: EdgeInsets.only(left: 3, right: 3), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + ), + TextSpan( + text: "检查更新", + style: TextStyle( + height: 1.3, + color: Theme.of(context).colorScheme.primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () => manualCheckNewVersion(context), + ), + ], + ), + ); + } + + Widget _buildDirty() { + return Text.rich( + TextSpan( + text: "下载RELEASE版", + style: TextStyle( + height: 1.3, + color: Theme.of(context).colorScheme.primary, + ), + recognizer: TapGestureRecognizer()..onTap = () => openUrl(_releasesUrl), + ), + ); + } } diff --git a/lib/screens/AppScreen.dart b/lib/screens/AppScreen.dart index 0ffc5f7..43cce7d 100644 --- a/lib/screens/AppScreen.dart +++ b/lib/screens/AppScreen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:pikapika/basic/config/Version.dart'; +import 'package:pikapika/screens/components/Badge.dart'; import 'CategoriesScreen.dart'; import 'SpaceScreen.dart'; @@ -12,20 +14,26 @@ class AppScreen extends StatefulWidget { } class _AppScreenState extends State { + @override + void initState() { + versionEvent.subscribe(_onVersion); + super.initState(); + } + + @override + void dispose() { + versionEvent.unsubscribe(_onVersion); + super.dispose(); + } + + void _onVersion(dynamic a) { + setState(() {}); + } + static const List _widgetOptions = [ const CategoriesScreen(), const SpaceScreen(), ]; - static const _navigationItems = [ - const BottomNavigationBarItem( - icon: Icon(Icons.public), - label: '浏览', - ), - const BottomNavigationBarItem( - icon: Icon(Icons.face), - label: '我的', - ), - ]; late int _selectedIndex = 0; @@ -43,7 +51,19 @@ class _AppScreenState extends State { children: _widgetOptions, ), bottomNavigationBar: BottomNavigationBar( - items: _navigationItems, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.public), + label: '浏览', + ), + BottomNavigationBarItem( + icon: Badged( + child: Icon(Icons.face), + badge: latestVersion() == null ? null : "1", + ), + label: '我的', + ), + ], currentIndex: _selectedIndex, iconSize: 20, selectedFontSize: 12, diff --git a/lib/screens/InitScreen.dart b/lib/screens/InitScreen.dart index 2b2273c..a6abb2d 100644 --- a/lib/screens/InitScreen.dart +++ b/lib/screens/InitScreen.dart @@ -71,7 +71,7 @@ class _InitScreenState extends State { await initDownloadThreadCount(); await initConvertToPNG(); await initVersion(); - await autoCheckNewVersion(); + autoCheckNewVersion(); // 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟 if (await method.preLogin()) { // 如果token或username+password有效则直接进入登录好的界面 diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index 8e3159c..3fa3606 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -22,6 +21,7 @@ import 'package:pikapika/basic/config/Quality.dart'; import 'package:pikapika/basic/config/ShadowCategories.dart'; import 'package:pikapika/basic/config/Themes.dart'; import 'package:pikapika/basic/config/TimeOffsetHour.dart'; +import 'package:pikapika/basic/config/Version.dart'; import 'package:pikapika/basic/config/VolumeController.dart'; import 'package:pikapika/screens/components/NetworkSetting.dart'; @@ -72,6 +72,9 @@ class SettingsScreen extends StatelessWidget { fontSetting(), Divider(), migrate(context), + Divider(), + autoUpdateCheckSetting(), + Divider(), ], ), ); diff --git a/lib/screens/SpaceScreen.dart b/lib/screens/SpaceScreen.dart index 065cb7a..a154626 100644 --- a/lib/screens/SpaceScreen.dart +++ b/lib/screens/SpaceScreen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/config/Themes.dart'; +import 'package:pikapika/basic/config/Version.dart'; import 'package:pikapika/screens/AboutScreen.dart'; import 'package:pikapika/screens/AccountScreen.dart'; import 'package:pikapika/screens/DownloadListScreen.dart'; @@ -9,6 +10,7 @@ import 'package:pikapika/screens/ViewLogsScreen.dart'; import 'package:pikapika/basic/Method.dart'; import 'SettingsScreen.dart'; +import 'components/Badge.dart'; import 'components/UserProfileCard.dart'; // 个人空间页面 @@ -20,11 +22,23 @@ class SpaceScreen extends StatefulWidget { } class _SpaceScreenState extends State { + @override void initState() { + versionEvent.subscribe(_onVersion); super.initState(); } + @override + void dispose() { + versionEvent.unsubscribe(_onVersion); + super.dispose(); + } + + void _onVersion(dynamic a) { + setState(() {}); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -53,7 +67,10 @@ class _SpaceScreenState extends State { MaterialPageRoute(builder: (context) => AboutScreen()), ); }, - icon: Icon(Icons.info_outline), + icon: Badged( + child: Icon(Icons.info_outline), + badge: latestVersion() == null ? null : "1", + ), ), IconButton( onPressed: () { diff --git a/lib/screens/components/Badge.dart b/lib/screens/components/Badge.dart new file mode 100644 index 0000000..cd852c3 --- /dev/null +++ b/lib/screens/components/Badge.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +class Badged extends StatelessWidget { + final String? badge; + final Widget child; + + const Badged({Key? key, required this.child, this.badge}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (badge == null) { + return child; + } + return Stack( + children: [ + child, + new Positioned( + right: 0, + child: new Container( + padding: EdgeInsets.all(1), + decoration: new BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(6), + ), + constraints: BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: new Text( + badge!, + style: new TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ); + } +} diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 121ad80..3c8c305 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,13 +31,13 @@ - - + + - -