auto upgrade

This commit is contained in:
niuhuan 2021-11-11 17:05:48 +08:00
parent d2b2265900
commit ed8aa0d57d
12 changed files with 372 additions and 50 deletions

View File

@ -10,7 +10,7 @@
<application <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="pikapi" android:label="pikapika"
android:requestLegacyExternalStorage="true"> android:requestLegacyExternalStorage="true">
<!-- requestLegacyExternalStorage="true" api29 down --> <!-- requestLegacyExternalStorage="true" api29 down -->
<activity <activity

View File

@ -1 +1 @@
gomobile bind -target=android/arm,android/arm64,android/386 -o lib/Pikapi.aar ./ gomobile bind -target=android/arm,android/arm64,android/386 -o lib/Mobile.aar ./

View File

@ -1 +1 @@
gomobile bind -target=android/arm -o lib/Pikapi.aar ./ gomobile bind -target=android/arm -o lib/Mobile.aar ./

View File

@ -99,12 +99,10 @@ Future<T?> chooseListDialog<T>(
{String? tips}) async { {String? tips}) async {
List<Widget> widgets = []; List<Widget> widgets = [];
if (tips != null) { if (tips != null) {
widgets.add( widgets.add(Container(
Container( padding: EdgeInsets.fromLTRB(15, 5, 15, 15),
padding: EdgeInsets.fromLTRB(15, 5, 15, 15), child: Text(tips),
child: Text(tips), ));
)
);
} }
widgets.addAll(items.map((e) => SimpleDialogOption( widgets.addAll(items.map((e) => SimpleDialogOption(
onPressed: () { onPressed: () {

View File

@ -1,35 +1,155 @@
import 'dart:async' show Future; 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:flutter/services.dart' show rootBundle;
import 'package:pikapika/basic/Common.dart';
import '../Method.dart'; import '../Method.dart';
const _versionUrl = 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'; const _versionAssets = 'lib/assets/version.txt';
RegExp _versionExp = RegExp(r"^v\d+\.\d+.\d+$"); RegExp _versionExp = RegExp(r"^v\d+\.\d+.\d+$");
late String _version; late String _version;
var _latestVersion = ""; String? _latestVersion;
const _propertyName = "checkVersionPeriod";
late int _period = -1;
Future initVersion() async { Future initVersion() async {
//
try { try {
_version = (await rootBundle.loadString(_versionAssets)).trim(); _version = (await rootBundle.loadString(_versionAssets)).trim();
} catch (e) { } catch (e) {
_version = "dirty"; _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<EventArgs>();
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 { Future _versionCheck() async {
if (_versionExp.hasMatch(_version)) { if (_versionExp.hasMatch(_version)) {
// exception var json = jsonDecode(await method.httpGet(_versionUrl));
String latestVersion = (await method.httpGet(_versionUrl)).trim(); if (json["name"] != null) {
if (latestVersion != _version) { String latestVersion = (json["name"]);
// new Version if (latestVersion != _version) {
_latestVersion = latestVersion;
}
} }
} else { } // else dirtyVersion
// 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 "-";
}
} }

View File

@ -1,13 +1,42 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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<StatefulWidget> createState() => _AboutScreenState();
}
class _AboutScreenState extends State<AboutScreen> {
@override
void initState() {
versionEvent.subscribe(_onVersion);
super.initState();
}
@override
void dispose() {
versionEvent.unsubscribe(_onVersion);
super.dispose();
}
void _onVersion(dynamic a) {
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var size = MediaQuery.of(context).size; var size = MediaQuery.of(context).size;
var min = size.width < size.height ? size.width : size.height; var min = size.width < size.height ? size.width : size.height;
var _currentVersion = currentVersion();
var _latestVersion = latestVersion();
var _dirty = dirtyVersion();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('关于'), title: Text('关于'),
@ -26,15 +55,37 @@ class AboutScreen extends StatelessWidget {
), ),
), ),
), ),
Container(height: 20),
Divider(),
Container( Container(
padding: EdgeInsets.all(20), padding: EdgeInsets.only(left: 20, right: 20),
child: Text( child: Column(
'请从软件取得渠道获取更新\n本软件开源, 若您想提出改进建议或者获取源码, 请在开源社区搜索 pikapi', crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle( children: [
height: 1.3, 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( Container(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
child: SelectableText( 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),
),
);
}
} }

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pikapika/basic/config/Version.dart';
import 'package:pikapika/screens/components/Badge.dart';
import 'CategoriesScreen.dart'; import 'CategoriesScreen.dart';
import 'SpaceScreen.dart'; import 'SpaceScreen.dart';
@ -12,20 +14,26 @@ class AppScreen extends StatefulWidget {
} }
class _AppScreenState extends State<AppScreen> { class _AppScreenState extends State<AppScreen> {
@override
void initState() {
versionEvent.subscribe(_onVersion);
super.initState();
}
@override
void dispose() {
versionEvent.unsubscribe(_onVersion);
super.dispose();
}
void _onVersion(dynamic a) {
setState(() {});
}
static const List<Widget> _widgetOptions = <Widget>[ static const List<Widget> _widgetOptions = <Widget>[
const CategoriesScreen(), const CategoriesScreen(),
const SpaceScreen(), const SpaceScreen(),
]; ];
static const _navigationItems = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: Icon(Icons.public),
label: '浏览',
),
const BottomNavigationBarItem(
icon: Icon(Icons.face),
label: '我的',
),
];
late int _selectedIndex = 0; late int _selectedIndex = 0;
@ -43,7 +51,19 @@ class _AppScreenState extends State<AppScreen> {
children: _widgetOptions, children: _widgetOptions,
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
items: _navigationItems, items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.public),
label: '浏览',
),
BottomNavigationBarItem(
icon: Badged(
child: Icon(Icons.face),
badge: latestVersion() == null ? null : "1",
),
label: '我的',
),
],
currentIndex: _selectedIndex, currentIndex: _selectedIndex,
iconSize: 20, iconSize: 20,
selectedFontSize: 12, selectedFontSize: 12,

View File

@ -71,7 +71,7 @@ class _InitScreenState extends State<InitScreen> {
await initDownloadThreadCount(); await initDownloadThreadCount();
await initConvertToPNG(); await initConvertToPNG();
await initVersion(); await initVersion();
await autoCheckNewVersion(); autoCheckNewVersion();
// , token失效重新登录, 1 // , token失效重新登录, 1
if (await method.preLogin()) { if (await method.preLogin()) {
// token或username+password有效则直接进入登录好的界面 // token或username+password有效则直接进入登录好的界面

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; 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/ShadowCategories.dart';
import 'package:pikapika/basic/config/Themes.dart'; import 'package:pikapika/basic/config/Themes.dart';
import 'package:pikapika/basic/config/TimeOffsetHour.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/basic/config/VolumeController.dart';
import 'package:pikapika/screens/components/NetworkSetting.dart'; import 'package:pikapika/screens/components/NetworkSetting.dart';
@ -72,6 +72,9 @@ class SettingsScreen extends StatelessWidget {
fontSetting(), fontSetting(),
Divider(), Divider(),
migrate(context), migrate(context),
Divider(),
autoUpdateCheckSetting(),
Divider(),
], ],
), ),
); );

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pikapika/basic/Common.dart'; import 'package:pikapika/basic/Common.dart';
import 'package:pikapika/basic/config/Themes.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/AboutScreen.dart';
import 'package:pikapika/screens/AccountScreen.dart'; import 'package:pikapika/screens/AccountScreen.dart';
import 'package:pikapika/screens/DownloadListScreen.dart'; import 'package:pikapika/screens/DownloadListScreen.dart';
@ -9,6 +10,7 @@ import 'package:pikapika/screens/ViewLogsScreen.dart';
import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/Method.dart';
import 'SettingsScreen.dart'; import 'SettingsScreen.dart';
import 'components/Badge.dart';
import 'components/UserProfileCard.dart'; import 'components/UserProfileCard.dart';
// //
@ -20,11 +22,23 @@ class SpaceScreen extends StatefulWidget {
} }
class _SpaceScreenState extends State<SpaceScreen> { class _SpaceScreenState extends State<SpaceScreen> {
@override @override
void initState() { void initState() {
versionEvent.subscribe(_onVersion);
super.initState(); super.initState();
} }
@override
void dispose() {
versionEvent.unsubscribe(_onVersion);
super.dispose();
}
void _onVersion(dynamic a) {
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -53,7 +67,10 @@ class _SpaceScreenState extends State<SpaceScreen> {
MaterialPageRoute(builder: (context) => AboutScreen()), MaterialPageRoute(builder: (context) => AboutScreen()),
); );
}, },
icon: Icon(Icons.info_outline), icon: Badged(
child: Icon(Icons.info_outline),
badge: latestVersion() == null ? null : "1",
),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {

View File

@ -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,
),
),
),
],
);
}
}

View File

@ -15,7 +15,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "pikapi.app" BuildableName = "pikapika.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@ -31,13 +31,13 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "pikapi.app" BuildableName = "pikapika.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -54,13 +54,11 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "pikapi.app" BuildableName = "pikapika.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"
@ -73,7 +71,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "pikapi.app" BuildableName = "pikapika.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>