mirror of
https://github.com/qinglong-app/qinglong_app.git
synced 2025-10-09 16:48:19 +08:00
添加版本更新检测功能
This commit is contained in:
BIN
assets/images/update_bg_app_top.png
Normal file
BIN
assets/images/update_bg_app_top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
BIN
assets/images/update_ic_close.png
Normal file
BIN
assets/images/update_ic_close.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -76,4 +76,6 @@ class Url {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static String checkUpdateUrl = "https://raw.githubusercontent.com/qinglong-app/qinglong_app/main/version";
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:qinglong_app/module/env/env_page.dart';
|
||||
import 'package:qinglong_app/module/others/other_page.dart';
|
||||
import 'package:qinglong_app/module/task/task_page.dart';
|
||||
import 'package:move_to_background/move_to_background.dart';
|
||||
import 'package:qinglong_app/utils/update_utils.dart';
|
||||
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
@@ -30,6 +31,9 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
initTitles();
|
||||
_title = titles[0].title;
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -185,6 +189,16 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void update() async {
|
||||
String? result = await UpdateUtils().checkUpdate();
|
||||
if (result != null && result.isNotEmpty) {
|
||||
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v$result", onUpdate: () {
|
||||
UpdateUtils.launchURL(result);
|
||||
});
|
||||
updateDialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IndexBean {
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||
import 'package:qinglong_app/main.dart';
|
||||
import 'package:qinglong_app/module/login/user_bean.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
import 'package:qinglong_app/utils/update_utils.dart';
|
||||
import 'package:qinglong_app/utils/utils.dart';
|
||||
import 'package:flip_card/flip_card.dart';
|
||||
|
||||
@@ -57,6 +58,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
}
|
||||
getIt<UserInfoViewModel>().updateToken("");
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
update();
|
||||
if (useSecretLogin) {
|
||||
cardKey.currentState?.toggleCard();
|
||||
}
|
||||
@@ -483,4 +485,14 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void update() async {
|
||||
String? result = await UpdateUtils().checkUpdate();
|
||||
if (result != null && result.isNotEmpty) {
|
||||
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v${result}", onUpdate: () {
|
||||
UpdateUtils.launchURL(result);
|
||||
});
|
||||
updateDialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/utils/update_utils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
@@ -23,6 +24,20 @@ class _AboutPageState extends ConsumerState<AboutPage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
getInfo();
|
||||
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
void update() async {
|
||||
String? result = await UpdateUtils().checkUpdate(true);
|
||||
if (result != null && result.isNotEmpty) {
|
||||
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v${result}", onUpdate: () {
|
||||
UpdateUtils.launchURL(result);
|
||||
});
|
||||
updateDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -270,6 +270,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
child: Text(
|
||||
bean.name ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
|
||||
395
lib/utils/update_utils.dart
Normal file
395
lib/utils/update_utils.dart
Normal file
@@ -0,0 +1,395 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:qinglong_app/base/http/url.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../main.dart';
|
||||
|
||||
/// @author NewTab
|
||||
|
||||
class UpdateUtils {
|
||||
Future<String?> checkUpdate([bool remind = false]) async {
|
||||
try {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
String version = packageInfo.version;
|
||||
String url = Url.checkUpdateUrl;
|
||||
Dio dio = Dio(BaseOptions(
|
||||
connectTimeout: 50000,
|
||||
receiveTimeout: 50000,
|
||||
sendTimeout: 50000,
|
||||
));
|
||||
var response = await dio.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
if (version == response.data as String) {
|
||||
return null;
|
||||
}
|
||||
return response.data as String;
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
if (remind) {
|
||||
"无法连接到github服务器,版本更新检测失败".toast();
|
||||
}
|
||||
logger.e(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void launchURL(String version) async {
|
||||
try {
|
||||
await launch("https://github.com/qinglong-app/qinglong_app/releases/download/v$version/app-release-v$version.apk");
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///版本更新加提示框
|
||||
class UpdateDialog {
|
||||
bool _isShowing = false;
|
||||
late BuildContext _context;
|
||||
late UpdateWidget _widget;
|
||||
|
||||
UpdateDialog(BuildContext context,
|
||||
{double width = 0.0,
|
||||
required String title,
|
||||
required String updateContent,
|
||||
required VoidCallback onUpdate,
|
||||
double titleTextSize = 16.0,
|
||||
double contentTextSize = 14.0,
|
||||
double buttonTextSize = 14.0,
|
||||
double progress = -1.0,
|
||||
Color progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||
Image? topImage,
|
||||
double extraHeight = 5.0,
|
||||
double radius = 4.0,
|
||||
Color themeColor = Colors.red,
|
||||
bool enableIgnore = false,
|
||||
VoidCallback? onIgnore,
|
||||
bool isForce = false,
|
||||
String? updateButtonText,
|
||||
String? ignoreButtonText,
|
||||
VoidCallback? onClose}) {
|
||||
_context = context;
|
||||
_widget = UpdateWidget(
|
||||
width: width,
|
||||
title: title,
|
||||
updateContent: updateContent,
|
||||
onUpdate: onUpdate,
|
||||
titleTextSize: titleTextSize,
|
||||
contentTextSize: contentTextSize,
|
||||
buttonTextSize: buttonTextSize,
|
||||
progress: progress,
|
||||
topImage: topImage,
|
||||
extraHeight: extraHeight,
|
||||
radius: radius,
|
||||
themeColor: themeColor,
|
||||
progressBackgroundColor: progressBackgroundColor,
|
||||
enableIgnore: enableIgnore,
|
||||
onIgnore: onIgnore,
|
||||
isForce: isForce,
|
||||
updateButtonText: updateButtonText ?? '更新',
|
||||
ignoreButtonText: ignoreButtonText ?? '忽略此版本',
|
||||
onClose: onClose ?? () => dismiss());
|
||||
}
|
||||
|
||||
/// 显示弹窗
|
||||
Future<bool> show() {
|
||||
try {
|
||||
if (isShowing()) {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
showDialog<bool>(
|
||||
context: _context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(onWillPop: () => Future<bool>.value(false), child: _widget);
|
||||
});
|
||||
_isShowing = true;
|
||||
return Future<bool>.value(true);
|
||||
} catch (err) {
|
||||
_isShowing = false;
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// 隐藏弹窗
|
||||
Future<bool> dismiss() {
|
||||
try {
|
||||
if (_isShowing) {
|
||||
_isShowing = false;
|
||||
Navigator.pop(_context);
|
||||
return Future<bool>.value(true);
|
||||
} else {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
} catch (err) {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否显示
|
||||
bool isShowing() {
|
||||
return _isShowing;
|
||||
}
|
||||
|
||||
/// 更新进度
|
||||
void update(double progress) {
|
||||
if (isShowing()) {
|
||||
_widget.update(progress);
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示版本更新提示框
|
||||
static UpdateDialog showUpdate(BuildContext context,
|
||||
{double width = 0.0,
|
||||
required String title,
|
||||
required String updateContent,
|
||||
required VoidCallback onUpdate,
|
||||
double titleTextSize = 16.0,
|
||||
double contentTextSize = 14.0,
|
||||
double buttonTextSize = 14.0,
|
||||
double progress = -1.0,
|
||||
Color progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||
Image? topImage,
|
||||
double extraHeight = 5.0,
|
||||
double radius = 4.0,
|
||||
Color themeColor = Colors.red,
|
||||
bool enableIgnore = false,
|
||||
VoidCallback? onIgnore,
|
||||
String? updateButtonText,
|
||||
String? ignoreButtonText,
|
||||
bool isForce = false}) {
|
||||
final UpdateDialog dialog = UpdateDialog(context,
|
||||
width: width,
|
||||
title: title,
|
||||
updateContent: updateContent,
|
||||
onUpdate: onUpdate,
|
||||
titleTextSize: titleTextSize,
|
||||
contentTextSize: contentTextSize,
|
||||
buttonTextSize: buttonTextSize,
|
||||
progress: progress,
|
||||
topImage: topImage,
|
||||
extraHeight: extraHeight,
|
||||
radius: radius,
|
||||
themeColor: themeColor,
|
||||
progressBackgroundColor: progressBackgroundColor,
|
||||
enableIgnore: enableIgnore,
|
||||
isForce: isForce,
|
||||
updateButtonText: updateButtonText,
|
||||
ignoreButtonText: ignoreButtonText,
|
||||
onIgnore: onIgnore);
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UpdateWidget extends StatefulWidget {
|
||||
/// 对话框的宽度
|
||||
final double width;
|
||||
|
||||
/// 升级标题
|
||||
final String title;
|
||||
|
||||
/// 更新内容
|
||||
final String updateContent;
|
||||
|
||||
/// 标题文字的大小
|
||||
final double titleTextSize;
|
||||
|
||||
/// 更新文字内容的大小
|
||||
final double contentTextSize;
|
||||
|
||||
/// 按钮文字的大小
|
||||
final double buttonTextSize;
|
||||
|
||||
/// 顶部图片
|
||||
final Widget? topImage;
|
||||
|
||||
/// 拓展高度(适配顶部图片高度不一致的情况)
|
||||
final double extraHeight;
|
||||
|
||||
/// 边框圆角大小
|
||||
final double radius;
|
||||
|
||||
/// 主题颜色
|
||||
final Color themeColor;
|
||||
|
||||
/// 更新事件
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
/// 可忽略更新
|
||||
final bool enableIgnore;
|
||||
|
||||
/// 更新事件
|
||||
final VoidCallback? onIgnore;
|
||||
|
||||
double progress;
|
||||
|
||||
/// 进度条的背景颜色
|
||||
final Color progressBackgroundColor;
|
||||
|
||||
/// 更新事件
|
||||
final VoidCallback? onClose;
|
||||
|
||||
/// 是否是强制更新
|
||||
final bool isForce;
|
||||
|
||||
/// 更新按钮内容
|
||||
final String updateButtonText;
|
||||
|
||||
/// 忽略按钮内容
|
||||
final String ignoreButtonText;
|
||||
|
||||
UpdateWidget(
|
||||
{Key? key,
|
||||
this.width = 0.0,
|
||||
required this.title,
|
||||
required this.updateContent,
|
||||
required this.onUpdate,
|
||||
this.titleTextSize = 16.0,
|
||||
this.contentTextSize = 14.0,
|
||||
this.buttonTextSize = 14.0,
|
||||
this.progress = -1.0,
|
||||
this.progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||
this.topImage,
|
||||
this.extraHeight = 5.0,
|
||||
this.radius = 4.0,
|
||||
this.themeColor = Colors.red,
|
||||
this.enableIgnore = false,
|
||||
this.onIgnore,
|
||||
this.isForce = false,
|
||||
this.updateButtonText = '更新',
|
||||
this.ignoreButtonText = '忽略此版本',
|
||||
this.onClose})
|
||||
: super(key: key);
|
||||
|
||||
final _UpdateWidgetState _state = _UpdateWidgetState();
|
||||
|
||||
void update(double progress) {
|
||||
_state.update(progress);
|
||||
}
|
||||
|
||||
@override
|
||||
_UpdateWidgetState createState() => _state;
|
||||
}
|
||||
|
||||
class _UpdateWidgetState extends State<UpdateWidget> {
|
||||
void update(double progress) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
widget.progress = progress;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double dialogWidth = widget.width <= 0 ? getFitWidth(context) * 0.618 : widget.width;
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SizedBox(
|
||||
width: dialogWidth,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: dialogWidth,
|
||||
child: widget.topImage ?? Image.asset('assets/images/update_bg_app_top.png', fit: BoxFit.fill),
|
||||
),
|
||||
Container(
|
||||
width: dialogWidth,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
|
||||
decoration: ShapeDecoration(
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(widget.radius), bottomRight: Radius.circular(widget.radius)),
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: widget.extraHeight),
|
||||
child: Text(widget.title, style: TextStyle(fontSize: widget.titleTextSize, color: Colors.black)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(widget.updateContent, style: TextStyle(fontSize: widget.contentTextSize, color: const Color(0xFF666666))),
|
||||
),
|
||||
Column(children: <Widget>[
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
textStyle: MaterialStateProperty.all(TextStyle(fontSize: widget.buttonTextSize)),
|
||||
foregroundColor: MaterialStateProperty.all(Colors.white),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))),
|
||||
elevation: MaterialStateProperty.all(0),
|
||||
backgroundColor: MaterialStateProperty.all(widget.themeColor),
|
||||
),
|
||||
child: Text(widget.updateButtonText),
|
||||
onPressed: widget.onUpdate,
|
||||
),
|
||||
),
|
||||
if (widget.enableIgnore && widget.onIgnore != null)
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
textStyle: MaterialStateProperty.all(TextStyle(fontSize: widget.buttonTextSize)),
|
||||
foregroundColor: MaterialStateProperty.all(const Color(0xFF666666)),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))),
|
||||
),
|
||||
child: Text(widget.ignoreButtonText),
|
||||
onPressed: widget.onIgnore,
|
||||
))
|
||||
else
|
||||
const SizedBox()
|
||||
]),
|
||||
],
|
||||
)),
|
||||
),
|
||||
if (!widget.isForce)
|
||||
Column(children: <Widget>[
|
||||
const SizedBox(width: 1.5, height: 50, child: DecoratedBox(decoration: BoxDecoration(color: Colors.white))),
|
||||
IconButton(
|
||||
iconSize: 30,
|
||||
constraints: const BoxConstraints(maxHeight: 30, maxWidth: 30),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Image.asset(
|
||||
'assets/images/update_ic_close.png',
|
||||
),
|
||||
onPressed: widget.onClose,
|
||||
)
|
||||
])
|
||||
else
|
||||
const SizedBox()
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
double getFitWidth(BuildContext context) {
|
||||
return min(getScreenHeight(context), getScreenWidth(context));
|
||||
}
|
||||
|
||||
double getScreenHeight(BuildContext context) {
|
||||
return MediaQuery.of(context).size.height;
|
||||
}
|
||||
|
||||
double getScreenWidth(BuildContext context) {
|
||||
return MediaQuery.of(context).size.width;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user