auto upgrade
This commit is contained in:
parent
d2b2265900
commit
ed8aa0d57d
|
@ -10,7 +10,7 @@
|
|||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="pikapi"
|
||||
android:label="pikapika"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<!-- requestLegacyExternalStorage="true" api29 down -->
|
||||
<activity
|
||||
|
|
|
@ -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 ./
|
||||
|
|
|
@ -1 +1 @@
|
|||
gomobile bind -target=android/arm -o lib/Pikapi.aar ./
|
||||
gomobile bind -target=android/arm -o lib/Mobile.aar ./
|
||||
|
|
|
@ -99,12 +99,10 @@ Future<T?> chooseListDialog<T>(
|
|||
{String? tips}) async {
|
||||
List<Widget> widgets = [];
|
||||
if (tips != null) {
|
||||
widgets.add(
|
||||
Container(
|
||||
widgets.add(Container(
|
||||
padding: EdgeInsets.fromLTRB(15, 5, 15, 15),
|
||||
child: Text(tips),
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
widgets.addAll(items.map((e) => SimpleDialogOption(
|
||||
onPressed: () {
|
||||
|
|
|
@ -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<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 {
|
||||
if (_versionExp.hasMatch(_version)) {
|
||||
// exception
|
||||
String latestVersion = (await method.httpGet(_versionUrl)).trim();
|
||||
var json = jsonDecode(await method.httpGet(_versionUrl));
|
||||
if (json["name"] != null) {
|
||||
String latestVersion = (json["name"]);
|
||||
if (latestVersion != _version) {
|
||||
// new 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 "-";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
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',
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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>[
|
||||
const CategoriesScreen(),
|
||||
const SpaceScreen(),
|
||||
];
|
||||
static const _navigationItems = <BottomNavigationBarItem>[
|
||||
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<AppScreen> {
|
|||
children: _widgetOptions,
|
||||
),
|
||||
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,
|
||||
iconSize: 20,
|
||||
selectedFontSize: 12,
|
||||
|
|
|
@ -71,7 +71,7 @@ class _InitScreenState extends State<InitScreen> {
|
|||
await initDownloadThreadCount();
|
||||
await initConvertToPNG();
|
||||
await initVersion();
|
||||
await autoCheckNewVersion();
|
||||
autoCheckNewVersion();
|
||||
// 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟
|
||||
if (await method.preLogin()) {
|
||||
// 如果token或username+password有效则直接进入登录好的界面
|
||||
|
|
|
@ -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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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<SpaceScreen> {
|
||||
|
||||
@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<SpaceScreen> {
|
|||
MaterialPageRoute(builder: (context) => AboutScreen()),
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.info_outline),
|
||||
icon: Badged(
|
||||
child: Icon(Icons.info_outline),
|
||||
badge: latestVersion() == null ? null : "1",
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "pikapi.app"
|
||||
BuildableName = "pikapika.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
@ -31,13 +31,13 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "pikapi.app"
|
||||
BuildableName = "pikapika.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
@ -54,13 +54,11 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "pikapi.app"
|
||||
BuildableName = "pikapika.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
|
@ -73,7 +71,7 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "pikapi.app"
|
||||
BuildableName = "pikapika.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
|
Loading…
Reference in New Issue