mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-10 07:48:55 +08:00
Add task detail page
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'task.g.dart';
|
||||
@@ -10,7 +12,21 @@ enum TaskType {
|
||||
@JsonValue(2)
|
||||
updateMeiliSearchData,
|
||||
@JsonValue(3)
|
||||
fixGalleryPage,
|
||||
fixGalleryPage;
|
||||
|
||||
String text(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
switch (this) {
|
||||
case TaskType.download:
|
||||
return i18n.downloadTask;
|
||||
case TaskType.exportZip:
|
||||
return i18n.exportZipTask;
|
||||
case TaskType.updateMeiliSearchData:
|
||||
return i18n.updateMeiliSearchDataTask;
|
||||
case TaskType.fixGalleryPage:
|
||||
return i18n.fixGalleryPageTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
@@ -189,7 +205,21 @@ enum TaskStatus {
|
||||
@JsonValue(2)
|
||||
finished,
|
||||
@JsonValue(3)
|
||||
failed,
|
||||
failed;
|
||||
|
||||
String text(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
switch (this) {
|
||||
case TaskStatus.wait:
|
||||
return i18n.waiting;
|
||||
case TaskStatus.running:
|
||||
return i18n.running;
|
||||
case TaskStatus.finished:
|
||||
return i18n.finished;
|
||||
case TaskStatus.failed:
|
||||
return i18n.failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TaskDetail {
|
||||
@@ -237,7 +267,7 @@ class TaskError {
|
||||
|
||||
@JsonSerializable()
|
||||
class DownloadConfig {
|
||||
DownloadConfig ({
|
||||
DownloadConfig({
|
||||
this.maxDownloadImgCount,
|
||||
this.mpv,
|
||||
this.downloadOriginalImg,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
import '../api/task.dart';
|
||||
import '../globals.dart';
|
||||
@@ -83,27 +84,31 @@ class _TaskView extends State<TaskView> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ReorderableDragStartListener(
|
||||
index: widget.index, child: const Icon(Icons.reorder))),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.push("/dialog/task/${widget.task.base.id}");
|
||||
},
|
||||
child: Column(children: [
|
||||
_buildText(context),
|
||||
LinearPercentIndicator(
|
||||
animation: true,
|
||||
animateFromLastPercent: true,
|
||||
progressColor: Colors.green,
|
||||
lineHeight: 20.0,
|
||||
barRadius: const Radius.circular(10),
|
||||
padding: EdgeInsets.zero,
|
||||
center: Text(percentText,
|
||||
style: const TextStyle(color: Colors.black)),
|
||||
percent: percent,
|
||||
),
|
||||
])),
|
||||
ReorderableDragStartListener(
|
||||
index: widget.index, child: const Icon(Icons.reorder)),
|
||||
])),
|
||||
_buildText(context),
|
||||
LinearPercentIndicator(
|
||||
animation: true,
|
||||
animateFromLastPercent: true,
|
||||
progressColor: Colors.green,
|
||||
lineHeight: 20.0,
|
||||
barRadius: const Radius.circular(10),
|
||||
padding: EdgeInsets.zero,
|
||||
center: Text(percentText,
|
||||
style: const TextStyle(color: Colors.black)),
|
||||
percent: percent,
|
||||
),
|
||||
]))),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eh_downloader_flutter/components/number_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import '../components/number_field.dart';
|
||||
import '../globals.dart';
|
||||
import '../utils/parse_url.dart';
|
||||
|
||||
|
||||
261
lib/dialog/task_page.dart
Normal file
261
lib/dialog/task_page.dart
Normal file
@@ -0,0 +1,261 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
import '../api/task.dart';
|
||||
import '../globals.dart';
|
||||
import '../utils/filesize.dart';
|
||||
|
||||
class _KeyValue extends StatelessWidget {
|
||||
const _KeyValue(this.name, this.value, {this.fontSize});
|
||||
final String name;
|
||||
final String value;
|
||||
final double? fontSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return Row(children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Center(
|
||||
child: Text(name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: cs.primary, fontSize: fontSize)))),
|
||||
Expanded(
|
||||
child: SelectableText(value,
|
||||
style: TextStyle(color: cs.secondary, fontSize: fontSize)),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class TaskPage extends StatefulWidget {
|
||||
const TaskPage(this.id, {super.key});
|
||||
final int id;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _TaskPage();
|
||||
}
|
||||
|
||||
class _TaskPage extends State<TaskPage> {
|
||||
void _onStateChanged(dynamic _) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _onProgressUpdated(dynamic arg) {
|
||||
final id = arg as int;
|
||||
if (id != widget.id) return;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
listener.on("task_list_changed", _onStateChanged);
|
||||
listener.on("task_meta_updated", _onStateChanged);
|
||||
listener.on("task_progress_updated", _onProgressUpdated);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
listener.removeEventListener("task_list_changed", _onStateChanged);
|
||||
listener.removeEventListener("task_meta_updated", _onStateChanged);
|
||||
listener.removeEventListener("task_progress_updated", _onProgressUpdated);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildBasicInfo(BuildContext context) {
|
||||
if (!tasks.tasksList.contains(widget.id)) return Container();
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
final task = tasks.tasks[widget.id]!;
|
||||
final typ = task.base.type;
|
||||
String gid = "";
|
||||
if (task.base.gid != 0) {
|
||||
gid = task.base.gid.toString();
|
||||
}
|
||||
if (task.base.gid == 0 &&
|
||||
(typ == TaskType.fixGalleryPage ||
|
||||
typ == TaskType.updateMeiliSearchData)) {
|
||||
gid = i18n.allGalleries;
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
_KeyValue(i18n.taskId, widget.id.toString(), fontSize: 16),
|
||||
_KeyValue(i18n.taskType, typ.text(context), fontSize: 16),
|
||||
_KeyValue(i18n.gid, gid, fontSize: 16),
|
||||
task.base.token.isEmpty
|
||||
? Container()
|
||||
: _KeyValue(i18n.galleryToken, task.base.token, fontSize: 16),
|
||||
_KeyValue(i18n.processId, task.base.pid.toString(), fontSize: 16),
|
||||
_KeyValue(i18n.taskStatus, task.status.text(context), fontSize: 16),
|
||||
task.fataled == null
|
||||
? Container()
|
||||
: _KeyValue(i18n.fatalError, task.fataled! ? i18n.yes : i18n.no,
|
||||
fontSize: 16),
|
||||
task.error == null
|
||||
? Container()
|
||||
: SelectableText(task.error!,
|
||||
style: const TextStyle(color: Colors.red)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool get haveProgress => tasks.tasksList.contains(widget.id)
|
||||
? tasks.tasks[widget.id]!.status == TaskStatus.running &&
|
||||
tasks.tasks[widget.id]!.progress != null
|
||||
: false;
|
||||
|
||||
Widget _buildProgress(BuildContext context) {
|
||||
if (!haveProgress) return Container();
|
||||
final task = tasks.tasks[widget.id]!;
|
||||
final typ = task.base.type;
|
||||
if (typ == TaskType.download) {
|
||||
final p = task.progress as TaskDownloadProgess;
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
if (p.totalPage == 0) {
|
||||
return Text(i18n.fetchingMetadata);
|
||||
}
|
||||
if (p.failedPage == 0) {
|
||||
final percent = p.downloadedPage / p.totalPage;
|
||||
final percentText = "${(percent * 100).toStringAsFixed(2)}%";
|
||||
return Row(children: [
|
||||
Expanded(
|
||||
child: LinearPercentIndicator(
|
||||
animation: true,
|
||||
animateFromLastPercent: true,
|
||||
progressColor: Colors.green,
|
||||
lineHeight: 20.0,
|
||||
barRadius: const Radius.circular(10),
|
||||
padding: EdgeInsets.zero,
|
||||
center:
|
||||
Text(percentText, style: const TextStyle(color: Colors.black)),
|
||||
percent: percent,
|
||||
)),
|
||||
Text("${p.downloadedPage}/${p.totalPage}"),
|
||||
]);
|
||||
}
|
||||
return Column(children: [
|
||||
_KeyValue(i18n.downloadedPages, p.downloadedPage.toString(),
|
||||
fontSize: 16),
|
||||
_KeyValue(i18n.failedPages, p.failedPage.toString(), fontSize: 16),
|
||||
_KeyValue(i18n.totalPages, p.totalPage.toString(), fontSize: 16),
|
||||
]);
|
||||
}
|
||||
int now = 0;
|
||||
int total = 0;
|
||||
switch (typ) {
|
||||
case TaskType.exportZip:
|
||||
final p = task.progress as TaskExportZipProgress;
|
||||
now = p.addedPage;
|
||||
total = p.totalPage;
|
||||
case TaskType.fixGalleryPage:
|
||||
final p = task.progress as TaskFixGalleryPageProgress;
|
||||
now = p.checkedGallery;
|
||||
total = p.totalGallery;
|
||||
case TaskType.updateMeiliSearchData:
|
||||
final p = task.progress as TaskUpdateMeiliSearchDataProgress;
|
||||
now = p.updatedGallery;
|
||||
total = p.totalGallery;
|
||||
default:
|
||||
}
|
||||
if (total == 0) return Container();
|
||||
final percent = now / total;
|
||||
final percentText = "${(percent * 100).toStringAsFixed(2)}%";
|
||||
return Row(children: [
|
||||
Expanded(
|
||||
child: LinearPercentIndicator(
|
||||
animation: true,
|
||||
animateFromLastPercent: true,
|
||||
progressColor: Colors.green,
|
||||
lineHeight: 20.0,
|
||||
barRadius: const Radius.circular(10),
|
||||
padding: EdgeInsets.zero,
|
||||
center: Text(percentText, style: const TextStyle(color: Colors.black)),
|
||||
percent: percent,
|
||||
)),
|
||||
Text("$now/$total"),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildMoreProgress(BuildContext context) {
|
||||
if (!haveProgress) return SliverToBoxAdapter(child: Container());
|
||||
final task = tasks.tasks[widget.id]!;
|
||||
if (task.base.type != TaskType.download) {
|
||||
return SliverToBoxAdapter(child: Container());
|
||||
}
|
||||
final p = task.progress as TaskDownloadProgess;
|
||||
if (p.details.isEmpty) return SliverToBoxAdapter(child: Container());
|
||||
return SliverList.builder(
|
||||
itemCount: p.details.length,
|
||||
itemBuilder: (context, index) {
|
||||
final d = p.details[index];
|
||||
final percent = d.downloaded / d.total;
|
||||
final percentText = "${(percent * 100).toStringAsFixed(2)}%";
|
||||
return Column(children: [
|
||||
Text("${d.name}(${d.width}x${d.height})"),
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: LinearPercentIndicator(
|
||||
animation: true,
|
||||
animateFromLastPercent: true,
|
||||
progressColor: Colors.green,
|
||||
lineHeight: 20.0,
|
||||
barRadius: const Radius.circular(10),
|
||||
padding: EdgeInsets.zero,
|
||||
center: Text(percentText,
|
||||
style: const TextStyle(color: Colors.black)),
|
||||
percent: percent,
|
||||
)),
|
||||
Text("${getFileSize(d.downloaded)}/${getFileSize(d.total)}"),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
tryInitApi(context);
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
final maxWidth = MediaQuery.of(context).size.width;
|
||||
final indent = maxWidth < 400 ? 5.0 : 10.0;
|
||||
return Container(
|
||||
padding: maxWidth < 400
|
||||
? const EdgeInsets.symmetric(vertical: 20, horizontal: 5)
|
||||
: const EdgeInsets.all(20),
|
||||
width: maxWidth < 810 ? null : 800,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Text(
|
||||
i18n.taskDetails,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
onPressed: () => context.canPop()
|
||||
? context.pop()
|
||||
: context.go("/task_manager"),
|
||||
icon: const Icon(Icons.close),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: _buildBasicInfo(context)),
|
||||
SliverToBoxAdapter(
|
||||
child: haveProgress
|
||||
? Divider(indent: indent, endIndent: indent)
|
||||
: Container()),
|
||||
SliverToBoxAdapter(child: _buildProgress(context)),
|
||||
_buildMoreProgress(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -156,5 +156,19 @@
|
||||
"galleryURL": "Gallery URL",
|
||||
"galleryToken": "Gallery Token",
|
||||
"randomFileSecret": "The secret of token to access random file without login",
|
||||
"downloadTask": "Download Task"
|
||||
"downloadTask": "Download Task",
|
||||
"taskDetails": "Task details",
|
||||
"taskId": "Task ID",
|
||||
"taskType": "Task type",
|
||||
"exportZipTask": "Export as ZIP file task",
|
||||
"updateMeiliSearchDataTask": "Sync meilisearch server's data task",
|
||||
"fixGalleryPageTask": "Fix gallery page data task",
|
||||
"allGalleries": "All galleries",
|
||||
"processId": "Process ID",
|
||||
"taskStatus": "Task status",
|
||||
"fatalError": "Fatal error",
|
||||
"fetchingMetadata": "Fetching metadata ...",
|
||||
"downloadedPages": "Downloaded pages",
|
||||
"failedPages": "Download failed pages",
|
||||
"totalPages": "Total pages"
|
||||
}
|
||||
|
||||
@@ -156,5 +156,19 @@
|
||||
"galleryURL": "画廊地址",
|
||||
"galleryToken": "画廊令牌",
|
||||
"randomFileSecret": "生成无需登录即可访问随机文件的令牌的密钥",
|
||||
"downloadTask": "下载任务"
|
||||
"downloadTask": "下载任务",
|
||||
"taskDetails": "任务详情",
|
||||
"taskId": "任务ID",
|
||||
"taskType": "任务类型",
|
||||
"exportZipTask": "导出为ZIP文件任务",
|
||||
"updateMeiliSearchDataTask": "同步meilisearch服务器数据任务",
|
||||
"fixGalleryPageTask": "修复画廊页面数据任务",
|
||||
"allGalleries": "所有画廊",
|
||||
"processId": "进程ID",
|
||||
"taskStatus": "任务状态",
|
||||
"fatalError": "致命错误",
|
||||
"fetchingMetadata": "获取元数据中…",
|
||||
"downloadedPages": "已下载页数",
|
||||
"failedPages": "下载失败的页数",
|
||||
"totalPages": "总页数"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'dialog/dialog_page.dart';
|
||||
import 'dialog/download_zip_page.dart';
|
||||
import 'dialog/gallery_details_page.dart';
|
||||
import 'dialog/new_download_task_page.dart';
|
||||
import 'dialog/task_page.dart';
|
||||
import 'galleries.dart';
|
||||
import 'gallery.dart';
|
||||
import 'globals.dart';
|
||||
@@ -183,7 +184,25 @@ final _router = GoRouter(
|
||||
builder: (context) {
|
||||
return NewDownloadTaskPage(gid: gid, token: token);
|
||||
});
|
||||
})
|
||||
}),
|
||||
GoRoute(
|
||||
path: "/dialog/task/:id",
|
||||
pageBuilder: (context, state) {
|
||||
return DialogPage(
|
||||
key: state.pageKey,
|
||||
builder: (context) {
|
||||
return TaskPage(int.parse(state.pathParameters["id"]!));
|
||||
});
|
||||
},
|
||||
redirect: (context, state) {
|
||||
try {
|
||||
int.parse(state.pathParameters["id"]!);
|
||||
return null;
|
||||
} catch (e) {
|
||||
_routerLog.warning("Failed to parse id:", e);
|
||||
return "/task_manager";
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user