Merge remote-tracking branch 'origin/master'

This commit is contained in:
PeanutMelonSeedBigAlmond
2024-05-25 00:55:26 +08:00
8 changed files with 179 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
import 'package:json_annotation/json_annotation.dart';
import 'api_result.dart';
import '../globals.dart';
part 'eh.g.dart';
@@ -75,6 +76,12 @@ class GalleryMetadataSingle {
factory GalleryMetadataSingle.fromJson(Map<String, dynamic> json) =>
_$GalleryMetadataSingleFromJson(json);
Map<String, dynamic> toJson() => _$GalleryMetadataSingleToJson(this);
String get preferredTitle => prefs.getBool("useTitleJpn") == true
? titleJpn.isEmpty
? title
: titleJpn
: title;
}
class EHMetaInfo {

109
lib/components/task.dart Normal file
View 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)),
])),
);
}
}

View File

@@ -155,5 +155,6 @@
"createDownloadTask": "Create Download Task",
"galleryURL": "Gallery URL",
"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"
}

View File

@@ -155,5 +155,6 @@
"createDownloadTask": "新建下载任务",
"galleryURL": "画廊地址",
"galleryToken": "画廊令牌",
"randomFileSecret": "生成无需登录即可访问随机文件的令牌的密钥"
"randomFileSecret": "生成无需登录即可访问随机文件的令牌的密钥",
"downloadTask": "下载任务"
}

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'api/eh.dart';
import 'api/task.dart';
import 'globals.dart';
import 'utils/websocket.dart';
@@ -15,6 +16,10 @@ class TaskManager {
bool _allowReconnect = true;
Timer? _reconnectTimer;
List<int> tasksList = [];
Map<int, GalleryMetadataSingle> meta = {};
bool _isFetching = false;
List<int> peddingGids = [];
List<String> peddingTokens = [];
void clear() {
tasks.clear();
_channel?.stream.drain();
@@ -22,7 +27,36 @@ class TaskManager {
_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) {
if (task.type == TaskType.download && !meta.containsKey(task.gid)) {
peddingGids.add(task.gid);
peddingTokens.add(task.token);
}
if (status == TaskStatus.finished) {
tasksList.add(task.id);
return;
@@ -66,6 +100,7 @@ class TaskManager {
addToTasksList(task, status);
}
listener.tryEmit("task_list_changed", null);
fetchMeta();
} else if (type == "new_task") {
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
tasks[task.id] = TaskDetail(
@@ -74,6 +109,7 @@ class TaskManager {
);
addToTasksList(task, TaskStatus.wait);
listener.tryEmit("task_list_changed", null);
fetchMeta();
} else if (type == "task_started") {
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
tasks.update(task.id, (value) {
@@ -83,6 +119,7 @@ class TaskManager {
return value;
}, ifAbsent: () {
addToTasksList(task, TaskStatus.running);
fetchMeta();
return TaskDetail(
base: task,
status: TaskStatus.running,
@@ -99,6 +136,7 @@ class TaskManager {
return value;
});
listener.tryEmit("task_list_changed", null);
fetchMeta();
}
} else if (type == "task_progress") {
final task =
@@ -108,6 +146,7 @@ class TaskManager {
value.progress = task.detail;
return value;
});
listener.tryEmit("task_progress_updated", task.taskId);
}
} else if (type == "task_updated") {
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);

View File

@@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'api/task.dart';
import 'components/task.dart';
import 'globals.dart';
enum TaskStatusFilterFlag with EnumFlag {
@@ -98,7 +99,7 @@ class _TaskManagerPage extends State<TaskManagerPage>
return Padding(
padding: const EdgeInsets.all(8),
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) {
@@ -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) {
return SliverReorderableList(

View File

@@ -654,6 +654,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View File

@@ -34,6 +34,7 @@ dependencies:
palette_generator: ^0.3.3+3
path: ^1.8.3
path_provider: ^2.1.0
percent_indicator: ^4.2.3
photo_view: ^0.15.0
retrofit: ^4.0.1
shared_preferences: ^2.2.0