同步ios的功能

This commit is contained in:
jyuesong
2022-05-30 14:02:14 +08:00
parent 578bb926fa
commit bd77beb60a
14 changed files with 412 additions and 548 deletions

View File

@ -105,7 +105,8 @@ class Routes {
case routeTaskLogDetail:
return CupertinoPageRoute(
builder: (context) => TaskLogDetailPage(
title: settings.arguments as String,
title: (settings.arguments as Map)['title'],
path: (settings.arguments as Map)['path'],
),
);
case routeScriptDetail:

View File

@ -10,7 +10,7 @@ import 'package:qinglong_app/utils/sp_utils.dart';
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
const commonColor = Color(0xFF299343);
Color commonColor = const Color(0xFF299343);
Color _primaryColor = commonColor;
class ThemeViewModel extends ChangeNotifier {
@ -71,7 +71,10 @@ class ThemeViewModel extends ChangeNotifier {
),
scaffoldBackgroundColor: const Color(0xfff5f5f5),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(color: _primaryColor),
labelStyle: TextStyle(
color: _primaryColor,
fontSize: 14,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: _primaryColor,
@ -152,7 +155,10 @@ class ThemeViewModel extends ChangeNotifier {
),
),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(color: _primaryColor),
labelStyle: TextStyle(
color: _primaryColor,
fontSize: 14,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: _primaryColor,
@ -208,12 +214,15 @@ class ThemeViewModel extends ChangeNotifier {
abstract class ThemeColors {
Color settingBgColor();
Color settingBordorColor();
Color titleColor();
Color descColor();
Color tabBarColor();
Color pinColor();
Color buttonBgColor();
@ -256,6 +265,11 @@ class LightThemeColors extends ThemeColors {
Color settingBordorColor() {
return Colors.white;
}
@override
Color tabBarColor() {
return const Color(0xffF7F7F7);
}
}
class DartThemeColors extends ThemeColors {
@ -293,4 +307,9 @@ class DartThemeColors extends ThemeColors {
Color settingBordorColor() {
return Color(0xff333333);
}
@override
Color tabBarColor() {
return Colors.black;
}
}

View File

@ -187,7 +187,7 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
envBean.remarks = _remarkController.text;
HttpResponse<NullResponse> response = await Api.addEnv(
_nameController.text, _valueController.text, _remarkController.text,
id: envBean.sId);
id: envBean.id,nId: envBean.nId,);
if (response.success) {
(envBean.sId == null) ? "新增成功" : "修改成功".toast();

View File

@ -4,6 +4,8 @@ import 'package:json_conversion_annotation/json_conversion_annotation.dart';
class EnvBean {
String? value;
String? sId;
String? _id;
int? id;
int? created;
int? status;
String? timestamp;
@ -12,8 +14,12 @@ class EnvBean {
EnvBean({this.value, this.sId, this.created, this.status, this.timestamp, this.name, this.remarks});
get nId => _id;
EnvBean.fromJson(Map<String, dynamic> json) {
value = json['value'];
id = json['id'];
_id = json['_id'];
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
created = int.tryParse(json['created'].toString());
status = json['status'];

View File

@ -8,11 +8,9 @@ import 'package:qinglong_app/base/ql_app_bar.dart';
import 'package:qinglong_app/base/routes.dart';
import 'package:qinglong_app/module/config/config_page.dart';
import 'package:qinglong_app/module/env/env_page.dart';
import 'package:qinglong_app/module/home/system_bean.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';
import 'dart:math' as math;
import '../../utils/utils.dart';
@ -47,7 +45,6 @@ class _HomePageState extends ConsumerState<HomePage>
_title = titles[0].title;
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
update();
getSystemBean();
});
}
@ -243,17 +240,6 @@ 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();
}
}
void isNewYearDuration() {
DateTime date = DateTime.now();
DateTime dateYear1 = DateTime(2022, 1, 29);

View File

@ -3,16 +3,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/http/api.dart';
import 'package:qinglong_app/base/http/http.dart';
import 'package:qinglong_app/base/routes.dart';
import 'package:qinglong_app/base/theme.dart';
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/login_helper.dart';
import 'package:qinglong_app/utils/update_utils.dart';
import 'package:qinglong_app/utils/utils.dart';
import 'package:flip_card/flip_card.dart';
@ -66,7 +63,6 @@ class _LoginPageState extends ConsumerState<LoginPage> {
}
getIt<UserInfoViewModel>().updateToken("");
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
update();
if (useSecretLogin) {
cardKey.currentState?.toggleCard();
}
@ -522,16 +518,6 @@ 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();
}
}
Widget buildCell(UserInfoBean bean) {
return ListTile(
title: Text(

View File

@ -3,7 +3,6 @@ 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';
@ -25,19 +24,6 @@ class _AboutPageState extends ConsumerState<AboutPage> {
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

View File

@ -2,6 +2,11 @@ import 'package:code_text_field/code_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:highlight/languages/javascript.dart';
import 'package:highlight/languages/json.dart';
import 'package:highlight/languages/powershell.dart';
import 'package:highlight/languages/python.dart';
import 'package:highlight/languages/vbscript-html.dart';
import 'package:highlight/languages/yaml.dart';
import 'package:qinglong_app/base/http/api.dart';
import 'package:qinglong_app/base/http/http.dart';
import 'package:qinglong_app/base/ql_app_bar.dart';
@ -41,11 +46,32 @@ class _ScriptEditPageState extends ConsumerState<ScriptEditPage> {
});
}
getLanguageType(String title) {
if (title.endsWith(".js")) {
return javascript;
}
if (title.endsWith(".sh")) {
return powershell;
}
if (title.endsWith(".py")) {
return python;
}
if (title.endsWith(".json")) {
return json;
}
if (title.endsWith(".yaml")) {
return yaml;
}
return vbscriptHtml;
}
@override
Widget build(BuildContext context) {
_codeController ??= CodeController(
text: widget.content,
language: javascript,
language: getLanguageType(widget.title),
onChange: (value) {
result = value;
},
@ -91,10 +117,24 @@ class _ScriptEditPageState extends ConsumerState<ScriptEditPage> {
),
body: SafeArea(
top: false,
child: CodeField(
controller: _codeController!,
expands: true,
background: ref.watch(themeProvider).themeColor.settingBgColor(),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
child: CodeField(
controller: _codeController!,
expands: true,
wrap: true,
lineNumberStyle: LineNumberStyle(
width: 0,
margin: 0,
textStyle: TextStyle(
color: ref.watch(themeProvider).themeColor.descColor(),
),
),
background: ref.watch(themeProvider).themeColor.tabBarColor(),
),
),
),
);

View File

@ -20,6 +20,18 @@ class ScriptPage extends ConsumerStatefulWidget {
class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<ScriptPage> {
List<ScriptBean> list = [];
final TextEditingController _searchController = TextEditingController();
final TextEditingController _nameController = TextEditingController();
String? path;
@override
void initState() {
super.initState();
_searchController.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
@ -30,83 +42,167 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
Navigator.of(context).pop();
},
title: "脚本管理",
actions: [
InkWell(
onTap: () {
addScript();
},
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 15,
),
child: Center(
child: Icon(
CupertinoIcons.add,
size: 24,
color: Colors.white,
),
),
),
),
],
),
body: list.isEmpty
? const Center(
child: CupertinoActivityIndicator(),
)
child: CupertinoActivityIndicator(),
)
: ListView.builder(
itemBuilder: (context, index) {
ScriptBean item = list[index];
itemBuilder: (context, index) {
if (index == 0) {
return searchCell(ref);
}
return ColoredBox(
color: ref.watch(themeProvider).themeColor.settingBgColor(),
child: (item.children != null && item.children!.isNotEmpty)
? ExpansionTile(
title: Text(
item.title ?? "",
style: TextStyle(
color:
(item.disabled ?? false) ? ref.watch(themeProvider).themeColor.descColor() : ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 16,
),
),
children: item.children!
.map((e) => ListTile(
onTap: () {
Navigator.of(context).pushNamed(
Routes.routeScriptDetail,
arguments: {
"title": e.title,
"path": e.parent,
},
).then((value) {
if (value != null && value == true) {
loadData();
}
});
},
title: Text(
e.title ?? "",
style: TextStyle(
color: (item.disabled ?? false)
? ref.watch(themeProvider).themeColor.descColor()
: ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 14,
),
),
))
.toList(),
)
: ListTile(
onTap: () {
Navigator.of(context).pushNamed(
Routes.routeScriptDetail,
arguments: {
"title": item.title,
"path": "",
},
).then(
(value) {
if (value != null && value == true) {
loadData();
}
},
);
},
title: Text(
item.title ?? "",
style: TextStyle(
color:
(item.disabled ?? false) ? ref.watch(themeProvider).themeColor.descColor() : ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 16,
),
),
),
);
},
itemCount: list.length,
),
ScriptBean item = list[index - 1];
if (_searchController.text.isEmpty ||
(item.title?.contains(_searchController.text) ?? false) ||
(item.value?.contains(_searchController.text) ?? false) ||
((item.children?.where((e) {
return (e.title?.contains(_searchController.text) ?? false) || (e.value?.contains(_searchController.text) ?? false);
}).isNotEmpty ??
false))) {
return ColoredBox(
color: ref.watch(themeProvider).themeColor.settingBgColor(),
child: (item.children != null && item.children!.isNotEmpty)
? ExpansionTile(
title: Text(
item.title ?? "",
style: TextStyle(
color: (item.disabled ?? false)
? ref.watch(themeProvider).themeColor.descColor()
: ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 16,
),
),
children: item.children!
.where((element) {
if (_searchController.text.isEmpty) {
return true;
}
return (element.title?.contains(_searchController.text) ?? false) ||
(element.value?.contains(_searchController.text) ?? false);
})
.map((e) => ListTile(
onTap: () {
Navigator.of(context).pushNamed(
Routes.routeScriptDetail,
arguments: {
"title": e.title,
"path": e.parent,
},
).then((value) {
if (value != null && value == true) {
loadData();
}
});
},
title: Text(
e.title ?? "",
style: TextStyle(
color: (item.disabled ?? false)
? ref.watch(themeProvider).themeColor.descColor()
: ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 14,
),
),
))
.toList(),
)
: ListTile(
onTap: () {
Navigator.of(context).pushNamed(
Routes.routeScriptDetail,
arguments: {
"title": item.title,
"path": "",
},
).then(
(value) {
if (value != null && value == true) {
loadData();
}
},
);
},
title: Text(
item.title ?? "",
style: TextStyle(
color: (item.disabled ?? false)
? ref.watch(themeProvider).themeColor.descColor()
: ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 16,
),
),
),
);
} else {
return const SizedBox.shrink();
}
},
itemCount: list.length + 1,
),
);
}
Widget searchCell(WidgetRef context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
),
child: CupertinoSearchTextField(
onSubmitted: (value) {
setState(() {});
},
onSuffixTap: () {
_searchController.text = "";
setState(() {});
},
controller: _searchController,
borderRadius: BorderRadius.circular(
30,
),
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
suffixInsets: const EdgeInsets.only(
right: 15,
),
prefixInsets: const EdgeInsets.only(
top: 6,
bottom: 6,
left: 15,
),
placeholderStyle: TextStyle(
fontSize: 16,
color: context.watch(themeProvider).themeColor.descColor(),
),
style: const TextStyle(
fontSize: 16,
),
placeholder: "搜索",
),
);
}
@ -129,4 +225,139 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
void onLazyLoad() {
loadData();
}
String scriptPath = "";
void addScript() {
showCupertinoDialog(
useRootNavigator: false,
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text("新增脚本"),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
height: 10,
),
const Text(
"脚本名称:",
style: TextStyle(
fontSize: 14,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.all(4),
hintText: "请输入脚本名称",
hintStyle: TextStyle(
fontSize: 14,
),
),
autofocus: false,
),
),
const SizedBox(
height: 10,
),
const Text(
"脚本所属文件夹:",
style: TextStyle(
fontSize: 14,
),
),
const SizedBox(
height: 10,
),
DropdownButtonFormField<String>(
items: list
.where((element) => element.children?.isNotEmpty ?? false)
.map((e) => DropdownMenuItem(
value: e.value,
child: SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Text(
e.value ?? "",
maxLines: 2,
),
),
))
.toList()
..insert(
0,
DropdownMenuItem(
value: "",
child: SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: const Text(
"根目录",
maxLines: 2,
),
),
)),
value: scriptPath,
onChanged: (value) {
scriptPath = value ?? "";
},
),
],
),
actions: [
CupertinoDialogAction(
child: const Text(
"取消",
style: TextStyle(
color: Color(0xff999999),
),
),
onPressed: () {
Navigator.of(context).pop();
},
),
CupertinoDialogAction(
child: Text(
"确定",
style: TextStyle(
color: ref.watch(themeProvider).primaryColor,
),
),
onPressed: () async {
"提交中...".toast();
HttpResponse<NullResponse> response = await Api.addScript(
_nameController.text,
scriptPath,
"## created by 青龙客户端 ${DateTime.now()}\n\n",
);
if (response.success) {
"提交成功".toast();
Navigator.of(context).pop();
Navigator.of(context).pushNamed(
Routes.routeScriptUpdate,
arguments: {
"title": _nameController.text,
"path": scriptPath,
"content": "## created by 青龙客户端 ${DateTime.now()}\n\n",
},
).then((value) {
if (value != null && value == true) {
_nameController.text = "";
loadData();
}
});
} else {
(response.message ?? "").toast();
}
},
),
],
),
);
}
}

View File

@ -12,10 +12,12 @@ import 'package:qinglong_app/utils/extension.dart';
/// @author NewTab
class TaskLogDetailPage extends ConsumerStatefulWidget {
final String title;
final String path;
const TaskLogDetailPage({
Key? key,
required this.title,
required this.path,
}) : super(key: key);
@override
@ -38,21 +40,22 @@ class _TaskLogDetailPageState extends ConsumerState<TaskLogDetailPage> with Lazy
body: content == null
? const Center(child: CupertinoActivityIndicator())
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
child: SelectableText(
(content == null || content!.isEmpty) ? "暂无数据" : content!,
selectionHeightStyle: BoxHeightStyle.max,
selectionWidthStyle: BoxWidthStyle.max,
),
),
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
child: SelectableText(
(content == null || content!.isEmpty) ? "暂无数据" : content!,
selectionHeightStyle: BoxHeightStyle.max,
selectionWidthStyle: BoxWidthStyle.max,
),
),
);
}
Future<void> loadData() async {
HttpResponse<String> response = await Api.taskLogDetail(
widget.title,
widget.path,
);
if (response.success) {

View File

@ -206,7 +206,7 @@ class _AddTaskPageState extends ConsumerState<AddTaskPage> {
taskBean.schedule = _cronController.text;
HttpResponse<NullResponse> response = await Api.addTask(
_nameController.text, _commandController.text, _cronController.text,
id: taskBean.sId);
id: taskBean.id,);
if (response.success) {
(widget.taskBean?.sId == null) ? "新增成功" : "修改成功".toast();

View File

@ -11,6 +11,8 @@ class TaskBean {
String? schedule;
bool? saved;
String? sId;
int? id;
String? _id;
int? created;
int? status;
String? timestamp;
@ -24,20 +26,22 @@ class TaskBean {
TaskBean(
{this.name,
this.command,
this.schedule,
this.saved,
this.sId,
this.created,
this.status,
this.timestamp,
this.isSystem,
this.isDisabled,
this.logPath,
this.isPinned,
this.lastExecutionTime,
this.lastRunningTime,
this.pid});
this.command,
this.schedule,
this.saved,
this.sId,
this.created,
this.status,
this.timestamp,
this.isSystem,
this.isDisabled,
this.logPath,
this.isPinned,
this.lastExecutionTime,
this.lastRunningTime,
this.pid});
get nId => _id;
TaskBean.fromJson(Map<String, dynamic> json) {
try {
@ -45,6 +49,8 @@ class TaskBean {
command = json['command'].toString();
schedule = json['schedule'].toString();
saved = json['saved'];
id = json['id'];
_id = json['_id'];
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
created = int.tryParse(json['created'].toString());
status = json['status'];

View File

@ -6,7 +6,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:qinglong_app/base/base_state_widget.dart';
import 'package:qinglong_app/base/routes.dart';
import 'package:qinglong_app/base/single_account_page.dart';
import 'package:qinglong_app/base/theme.dart';
import 'package:qinglong_app/module/task/intime_log/intime_log_page.dart';
import 'package:qinglong_app/module/task/task_bean.dart';

View File

@ -1,399 +0,0 @@
import 'dart:io';
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 {
if (!Platform.isAndroid) {
return null;
}
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;
}
}