mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-07-08 01:30:28 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'api_result.dart';
|
import 'api_result.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
|
||||||
part 'eh.g.dart';
|
part 'eh.g.dart';
|
||||||
|
|
||||||
@@ -75,6 +76,12 @@ class GalleryMetadataSingle {
|
|||||||
factory GalleryMetadataSingle.fromJson(Map<String, dynamic> json) =>
|
factory GalleryMetadataSingle.fromJson(Map<String, dynamic> json) =>
|
||||||
_$GalleryMetadataSingleFromJson(json);
|
_$GalleryMetadataSingleFromJson(json);
|
||||||
Map<String, dynamic> toJson() => _$GalleryMetadataSingleToJson(this);
|
Map<String, dynamic> toJson() => _$GalleryMetadataSingleToJson(this);
|
||||||
|
|
||||||
|
String get preferredTitle => prefs.getBool("useTitleJpn") == true
|
||||||
|
? titleJpn.isEmpty
|
||||||
|
? title
|
||||||
|
: titleJpn
|
||||||
|
: title;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EHMetaInfo {
|
class EHMetaInfo {
|
||||||
|
|||||||
109
lib/components/task.dart
Normal file
109
lib/components/task.dart
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||||
|
import '../api/task.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
|
||||||
|
class TaskView extends StatefulWidget {
|
||||||
|
const TaskView(this.task, this.index, {super.key});
|
||||||
|
final TaskDetail task;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _TaskView();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskView extends State<TaskView> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
listener.on("task_meta_updated", _onStateChanged);
|
||||||
|
listener.on("task_progress_updated", _onProgressUpdated);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onStateChanged(dynamic _) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onProgressUpdated(dynamic arg) {
|
||||||
|
final id = arg as int;
|
||||||
|
if (id != widget.task.base.id) return;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
double get percent {
|
||||||
|
if (widget.task.status == TaskStatus.finished) return 1;
|
||||||
|
if (widget.task.status != TaskStatus.running) return 0;
|
||||||
|
if (widget.task.progress == null) return 0;
|
||||||
|
switch (widget.task.base.type) {
|
||||||
|
case TaskType.download:
|
||||||
|
final progress = widget.task.progress as TaskDownloadProgess;
|
||||||
|
return progress.downloadedPage / progress.totalPage;
|
||||||
|
case TaskType.exportZip:
|
||||||
|
final progress = widget.task.progress as TaskExportZipProgress;
|
||||||
|
return progress.addedPage / progress.totalPage;
|
||||||
|
case TaskType.fixGalleryPage:
|
||||||
|
final progress = widget.task.progress as TaskFixGalleryPageProgress;
|
||||||
|
return progress.checkedGallery / progress.totalGallery;
|
||||||
|
case TaskType.updateMeiliSearchData:
|
||||||
|
final progress =
|
||||||
|
widget.task.progress as TaskUpdateMeiliSearchDataProgress;
|
||||||
|
return progress.updatedGallery / progress.totalGallery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get percentText {
|
||||||
|
return "${(percent * 100).toStringAsFixed(2)}%";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
listener.removeEventListener("task_meta_updated", _onStateChanged);
|
||||||
|
listener.removeEventListener("task_progress_updated", _onProgressUpdated);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildText(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
if (widget.task.base.type == TaskType.download) {
|
||||||
|
final gid = widget.task.base.gid;
|
||||||
|
return Row(children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(i18n.downloadTask)),
|
||||||
|
Text(tasks.meta.containsKey(gid)
|
||||||
|
? tasks.meta[gid]!.preferredTitle
|
||||||
|
: gid.toString()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: Row(children: [
|
||||||
|
Expanded(
|
||||||
|
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)),
|
||||||
|
])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,5 +155,6 @@
|
|||||||
"createDownloadTask": "Create Download Task",
|
"createDownloadTask": "Create Download Task",
|
||||||
"galleryURL": "Gallery URL",
|
"galleryURL": "Gallery URL",
|
||||||
"galleryToken": "Gallery Token",
|
"galleryToken": "Gallery Token",
|
||||||
"randomFileSecret": "The secret of token to access random file without login"
|
"randomFileSecret": "The secret of token to access random file without login",
|
||||||
|
"downloadTask": "Download Task"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,5 +155,6 @@
|
|||||||
"createDownloadTask": "新建下载任务",
|
"createDownloadTask": "新建下载任务",
|
||||||
"galleryURL": "画廊地址",
|
"galleryURL": "画廊地址",
|
||||||
"galleryToken": "画廊令牌",
|
"galleryToken": "画廊令牌",
|
||||||
"randomFileSecret": "生成无需登录即可访问随机文件的令牌的密钥"
|
"randomFileSecret": "生成无需登录即可访问随机文件的令牌的密钥",
|
||||||
|
"downloadTask": "下载任务"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
import 'api/eh.dart';
|
||||||
import 'api/task.dart';
|
import 'api/task.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
import 'utils/websocket.dart';
|
import 'utils/websocket.dart';
|
||||||
@@ -15,6 +16,10 @@ class TaskManager {
|
|||||||
bool _allowReconnect = true;
|
bool _allowReconnect = true;
|
||||||
Timer? _reconnectTimer;
|
Timer? _reconnectTimer;
|
||||||
List<int> tasksList = [];
|
List<int> tasksList = [];
|
||||||
|
Map<int, GalleryMetadataSingle> meta = {};
|
||||||
|
bool _isFetching = false;
|
||||||
|
List<int> peddingGids = [];
|
||||||
|
List<String> peddingTokens = [];
|
||||||
void clear() {
|
void clear() {
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
_channel?.stream.drain();
|
_channel?.stream.drain();
|
||||||
@@ -22,7 +27,36 @@ class TaskManager {
|
|||||||
_closed = true;
|
_closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fetchMeta() async {
|
||||||
|
if (_isFetching) return;
|
||||||
|
try {
|
||||||
|
if (peddingGids.isEmpty) return;
|
||||||
|
_isFetching = true;
|
||||||
|
final re = (await api.getMetaInfo(peddingGids, peddingTokens)).unwrap();
|
||||||
|
for (final e in re.metas.entries) {
|
||||||
|
if (e.value.ok) {
|
||||||
|
meta[e.key] = e.value.unwrap();
|
||||||
|
final index = peddingGids.indexOf(e.key);
|
||||||
|
if (index > -1) {
|
||||||
|
peddingGids.removeAt(index);
|
||||||
|
peddingTokens.removeAt(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_log.warning("Gallery id ${e.key}:", e.value.unwrapErr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listener.tryEmit("task_meta_updated", null);
|
||||||
|
} catch (e) {
|
||||||
|
_log.warning("Failed to fetch metadatas:", e);
|
||||||
|
}
|
||||||
|
_isFetching = false;
|
||||||
|
}
|
||||||
|
|
||||||
void addToTasksList(Task task, TaskStatus status) {
|
void addToTasksList(Task task, TaskStatus status) {
|
||||||
|
if (task.type == TaskType.download && !meta.containsKey(task.gid)) {
|
||||||
|
peddingGids.add(task.gid);
|
||||||
|
peddingTokens.add(task.token);
|
||||||
|
}
|
||||||
if (status == TaskStatus.finished) {
|
if (status == TaskStatus.finished) {
|
||||||
tasksList.add(task.id);
|
tasksList.add(task.id);
|
||||||
return;
|
return;
|
||||||
@@ -66,6 +100,7 @@ class TaskManager {
|
|||||||
addToTasksList(task, status);
|
addToTasksList(task, status);
|
||||||
}
|
}
|
||||||
listener.tryEmit("task_list_changed", null);
|
listener.tryEmit("task_list_changed", null);
|
||||||
|
fetchMeta();
|
||||||
} else if (type == "new_task") {
|
} else if (type == "new_task") {
|
||||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||||
tasks[task.id] = TaskDetail(
|
tasks[task.id] = TaskDetail(
|
||||||
@@ -74,6 +109,7 @@ class TaskManager {
|
|||||||
);
|
);
|
||||||
addToTasksList(task, TaskStatus.wait);
|
addToTasksList(task, TaskStatus.wait);
|
||||||
listener.tryEmit("task_list_changed", null);
|
listener.tryEmit("task_list_changed", null);
|
||||||
|
fetchMeta();
|
||||||
} else if (type == "task_started") {
|
} else if (type == "task_started") {
|
||||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||||
tasks.update(task.id, (value) {
|
tasks.update(task.id, (value) {
|
||||||
@@ -83,6 +119,7 @@ class TaskManager {
|
|||||||
return value;
|
return value;
|
||||||
}, ifAbsent: () {
|
}, ifAbsent: () {
|
||||||
addToTasksList(task, TaskStatus.running);
|
addToTasksList(task, TaskStatus.running);
|
||||||
|
fetchMeta();
|
||||||
return TaskDetail(
|
return TaskDetail(
|
||||||
base: task,
|
base: task,
|
||||||
status: TaskStatus.running,
|
status: TaskStatus.running,
|
||||||
@@ -99,6 +136,7 @@ class TaskManager {
|
|||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
listener.tryEmit("task_list_changed", null);
|
listener.tryEmit("task_list_changed", null);
|
||||||
|
fetchMeta();
|
||||||
}
|
}
|
||||||
} else if (type == "task_progress") {
|
} else if (type == "task_progress") {
|
||||||
final task =
|
final task =
|
||||||
@@ -108,6 +146,7 @@ class TaskManager {
|
|||||||
value.progress = task.detail;
|
value.progress = task.detail;
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
listener.tryEmit("task_progress_updated", task.taskId);
|
||||||
}
|
}
|
||||||
} else if (type == "task_updated") {
|
} else if (type == "task_updated") {
|
||||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'api/task.dart';
|
import 'api/task.dart';
|
||||||
|
import 'components/task.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
|
|
||||||
enum TaskStatusFilterFlag with EnumFlag {
|
enum TaskStatusFilterFlag with EnumFlag {
|
||||||
@@ -98,7 +99,7 @@ class _TaskManagerPage extends State<TaskManagerPage>
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
key: ValueKey("task_${task.base.id}"),
|
key: ValueKey("task_${task.base.id}"),
|
||||||
child: Text("TODO ${task.base.id}"));
|
child: TaskView(task, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _proxyDecorator(Widget child, int index, Animation<double> animation) {
|
Widget _proxyDecorator(Widget child, int index, Animation<double> animation) {
|
||||||
@@ -116,7 +117,15 @@ class _TaskManagerPage extends State<TaskManagerPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onReorder(int oldIndex, int newIndex) {}
|
void _onReorder(int oldIndex, int newIndex) {
|
||||||
|
setState(() {
|
||||||
|
if (oldIndex < newIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final task = tasks.tasksList.removeAt(oldIndex);
|
||||||
|
tasks.tasksList.insert(newIndex, task);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildList(BuildContext context) {
|
Widget _buildList(BuildContext context) {
|
||||||
return SliverReorderableList(
|
return SliverReorderableList(
|
||||||
|
|||||||
@@ -654,6 +654,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
|
percent_indicator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: percent_indicator
|
||||||
|
sha256: c37099ad833a883c9d71782321cb65c3a848c21b6939b6185f0ff6640d05814c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.3"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ dependencies:
|
|||||||
palette_generator: ^0.3.3+3
|
palette_generator: ^0.3.3+3
|
||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
path_provider: ^2.1.0
|
path_provider: ^2.1.0
|
||||||
|
percent_indicator: ^4.2.3
|
||||||
photo_view: ^0.15.0
|
photo_view: ^0.15.0
|
||||||
retrofit: ^4.0.1
|
retrofit: ^4.0.1
|
||||||
shared_preferences: ^2.2.0
|
shared_preferences: ^2.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user