342 lines
9.5 KiB
Dart
342 lines
9.5 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
|
|
import 'package:uni_links/uni_links.dart';
|
|
import 'package:uri_to_file/uri_to_file.dart';
|
|
|
|
import '../screens/ComicInfoScreen.dart';
|
|
import '../screens/DownloadOnlyImportScreen.dart';
|
|
import '../screens/PkzArchiveScreen.dart';
|
|
import 'config/IconLoading.dart';
|
|
import 'config/TimeOffsetHour.dart';
|
|
|
|
/// 默认的图片尺寸
|
|
double coverWidth = 210;
|
|
double coverHeight = 315;
|
|
|
|
String categoryTitle(String? categoryTitle) {
|
|
return categoryTitle ?? "全分类";
|
|
}
|
|
|
|
/// 显示一个toast
|
|
void defaultToast(BuildContext context, String title) {
|
|
showToast(
|
|
title,
|
|
context: context,
|
|
position: StyledToastPosition.center,
|
|
animation: StyledToastAnimation.scale,
|
|
reverseAnimation: StyledToastAnimation.fade,
|
|
duration: const Duration(seconds: 4),
|
|
animDuration: const Duration(seconds: 1),
|
|
curve: Curves.elasticOut,
|
|
reverseCurve: Curves.linear,
|
|
);
|
|
}
|
|
|
|
/// 显示一个确认框, 用户关闭弹窗以及选择否都会返回false, 仅当用户选择确定时返回true
|
|
Future<bool> confirmDialog(
|
|
BuildContext context, String title, String content) async {
|
|
return await showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(title),
|
|
content: SingleChildScrollView(
|
|
child: ListBody(
|
|
children: <Widget>[Text(content)],
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
MaterialButton(
|
|
child: const Text('取消'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop(false);
|
|
},
|
|
),
|
|
MaterialButton(
|
|
child: const Text('确定'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop(true);
|
|
},
|
|
),
|
|
],
|
|
)) ??
|
|
false;
|
|
}
|
|
|
|
/// 显示一个消息提示框
|
|
Future alertDialog(BuildContext context, String title, String content) {
|
|
return showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(title),
|
|
content: SingleChildScrollView(
|
|
child: ListBody(
|
|
children: <Widget>[
|
|
Text(content),
|
|
],
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
MaterialButton(
|
|
child: const Text('确定'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// stream-filter的替代方法
|
|
List<T> filteredList<T>(List<T> list, bool Function(T) filter) {
|
|
List<T> result = [];
|
|
for (var element in list) {
|
|
if (filter(element)) {
|
|
result.add(element);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// 创建一个单选对话框, 用户取消选择返回null, 否则返回所选内容
|
|
Future<T?> chooseListDialog<T>(
|
|
BuildContext context, String title, List<T> items,
|
|
{String? tips}) async {
|
|
return showDialog<T>(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return SimpleDialog(
|
|
title: Text(title),
|
|
children: [
|
|
...items.map((e) => SimpleDialogOption(
|
|
onPressed: () {
|
|
Navigator.of(context).pop(e);
|
|
},
|
|
child: Text('$e'),
|
|
)),
|
|
...tips != null
|
|
? [
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(15, 5, 15, 15),
|
|
child: Text(tips),
|
|
),
|
|
]
|
|
: [],
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 创建一个单选对话框, 用户取消选择返回null, 否则返回所选内容(value)
|
|
Future<T?> chooseMapDialog<T>(
|
|
BuildContext buildContext, Map<String, T> values, String title) async {
|
|
return await showDialog<T>(
|
|
context: buildContext,
|
|
builder: (BuildContext context) {
|
|
return SimpleDialog(
|
|
title: Text(title),
|
|
children: values.entries
|
|
.map((e) => SimpleDialogOption(
|
|
child: Text(e.key),
|
|
onPressed: () {
|
|
Navigator.of(context).pop(e.value);
|
|
},
|
|
))
|
|
.toList(),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 输入对话框1
|
|
|
|
var _controller =
|
|
TextEditingController.fromValue(const TextEditingValue(text: ''));
|
|
|
|
Future<String?> displayTextInputDialog(BuildContext context,
|
|
{String? title,
|
|
String src = "",
|
|
String? hint,
|
|
String? desc,
|
|
bool isPasswd = false}) {
|
|
_controller.text = src;
|
|
return showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: title == null ? null : Text(title),
|
|
content: SingleChildScrollView(
|
|
child: ListBody(
|
|
children: [
|
|
TextField(
|
|
controller: _controller,
|
|
decoration: InputDecoration(hintText: hint),
|
|
obscureText: isPasswd,
|
|
obscuringCharacter: '\u2022',
|
|
),
|
|
...(desc == null
|
|
? []
|
|
: [
|
|
Container(
|
|
padding: const EdgeInsets.only(top: 20, bottom: 10),
|
|
child: Text(
|
|
desc,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Theme.of(context)
|
|
.textTheme
|
|
.bodyText1
|
|
?.color
|
|
?.withOpacity(.5)),
|
|
),
|
|
)
|
|
]),
|
|
],
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
MaterialButton(
|
|
child: const Text('取消'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
MaterialButton(
|
|
child: const Text('确认'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop(_controller.text);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 将字符串前面加0直至满足len位
|
|
String add0(int num, int len) {
|
|
var rsp = "$num";
|
|
while (rsp.length < len) {
|
|
rsp = "0$rsp";
|
|
}
|
|
return rsp;
|
|
}
|
|
|
|
/// 格式化时间 2012-34-56
|
|
String formatTimeToDate(String str) {
|
|
try {
|
|
var c = DateTime.parse(str).add(Duration(hours: currentTimeOffsetHour()));
|
|
return "${add0(c.year, 4)}-${add0(c.month, 2)}-${add0(c.day, 2)}";
|
|
} catch (e) {
|
|
return "-";
|
|
}
|
|
}
|
|
|
|
/// 格式化时间 2012-34-56 12:34:56
|
|
String formatTimeToDateTime(String str) {
|
|
try {
|
|
var c = DateTime.parse(str).add(Duration(hours: currentTimeOffsetHour()));
|
|
return "${add0(c.year, 4)}-${add0(c.month, 2)}-${add0(c.day, 2)} ${add0(c.hour, 2)}:${add0(c.minute, 2)}";
|
|
} catch (e) {
|
|
return "-";
|
|
}
|
|
}
|
|
|
|
/// 输入对话框2
|
|
|
|
final TextEditingController _textEditController =
|
|
TextEditingController(text: '');
|
|
|
|
Future<String?> inputString(BuildContext context, String title,
|
|
{String hint = "", String? defaultValue}) async {
|
|
if (defaultValue != null) {
|
|
_textEditController.text = defaultValue;
|
|
} else {
|
|
_textEditController.clear();
|
|
}
|
|
return showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
content: Card(
|
|
child: SingleChildScrollView(
|
|
child: ListBody(
|
|
children: [
|
|
Text(title),
|
|
TextField(
|
|
controller: _textEditController,
|
|
decoration: InputDecoration(
|
|
labelText: hint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
MaterialButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
child: const Text('取消'),
|
|
),
|
|
MaterialButton(
|
|
onPressed: () {
|
|
Navigator.pop(context, _textEditController.text);
|
|
},
|
|
child: const Text('确定'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
StreamSubscription<String?> linkSubscript(BuildContext context) {
|
|
return linkStream.listen((uri) async {
|
|
if (uri == null) return;
|
|
var parsed = Uri.parse(uri);
|
|
if (RegExp(r"^pika://comic/([0-9A-z]+)/$").allMatches(uri).isNotEmpty) {
|
|
String comicId = RegExp(r"^pika://comic/([0-9A-z]+)/$")
|
|
.allMatches(uri)
|
|
.first
|
|
.group(1)!;
|
|
Navigator.of(context).push(
|
|
mixRoute(
|
|
builder: (BuildContext context) => ComicInfoScreen(comicId: comicId),
|
|
),
|
|
);
|
|
} else if (RegExp(r"^https?://pika/comic/([0-9A-z]+)/$").allMatches(uri).isNotEmpty) {
|
|
String comicId = RegExp(r"^https?://pika/comic/([0-9A-z]+)/$")
|
|
.allMatches(uri)
|
|
.first
|
|
.group(1)!;
|
|
Navigator.of(context).push(
|
|
mixRoute(
|
|
builder: (BuildContext context) => ComicInfoScreen(comicId: comicId),
|
|
),
|
|
);
|
|
} else if (RegExp(r"^.*\.pkz$").allMatches(parsed.path).isNotEmpty) {
|
|
File file = await toFile(uri);
|
|
Navigator.of(context).push(
|
|
mixRoute(
|
|
builder: (BuildContext context) =>
|
|
PkzArchiveScreen(pkzPath: file.path),
|
|
),
|
|
);
|
|
} else if (RegExp(r"^.*\.((pki)|(zip))$").allMatches(parsed.path).isNotEmpty) {
|
|
File file = await toFile(uri);
|
|
Navigator.of(context).push(
|
|
mixRoute(
|
|
builder: (BuildContext context) =>
|
|
DownloadOnlyImportScreen(path: file.path),
|
|
),
|
|
);
|
|
}
|
|
});
|
|
}
|