Pica Comic v4.0.0
重构代码结构
v4.0.0的第一个commit为重构代码结构, 这不会对App的功能产生影响, 但能让项目更优雅
具体而言, 有以下的变化:
- 将
/views
拆分为/pages
和/components
- 内置漫画源改用
ComicSource
实现 - 使用统一的接口构建漫画块和漫画列表
- 删除无用代码, 优化许多代码的写法
- 提高代码的复用率
关键词屏蔽
v4.0.0之前, 关键词屏蔽是在网络请求时进行, 这导致对于每个漫画源都需要实现一次关键词屏蔽的逻辑.
现在关键词屏蔽在页面构建时进行, 这带来了两个好处:
可见的屏蔽
设置-浏览中新添加了一个选项: 完全隐藏屏蔽的作品, 默认值启用, 当禁用时会有这样的效果:
快速屏蔽
漫画块的右键菜单和长按菜单中添加了新的选项: 屏蔽
下载功能改进
改进下载逻辑
v4.0.0之前, 下载功能的实现简单粗暴: 按顺序对每个图片进行下载, 当在下载某张图片前, 要求ImageManager
同时缓存当前图片以及之后的多张图片, 然后等待当前缓存完成, 将其移动到下载目录.
现在的下载逻辑为, 按顺序检查每张需要下载的图片, 若该图片未被下载, 等待其下载完成. 在检查某张图片前, 或者某张图片下载完成时, 调用_scheduleTasks
分配下载任务. 这样确保始终有最大数量的并行图片下载任务.
void _scheduleTasks(int ep, int index) {
var urls = links![ep]!;
int downloading = 0;
for (int i = index; i < urls.length; i++) {
var task = _downloading["$ep$i"];
if (task == null || task.error != null) {
_addDownloading(urls[i], ep, i);
downloading++;
} else if (!task.isFinished) {
downloading++;
}
if (downloading >= allowedLoadingNumber) {
break;
}
}
}
图片下载监视
为了实现新的下载逻辑, 需要对图片下载进行监视, 不能像旧版本那样只是等待缓存完成. 图片的下载监视使用类_ImageDownloadWrapper
完成
class _ImageDownloadWrapper {
final Stream<DownloadProgress> stream;
final String path;
final String fileBaseName;
final void Function(int length) onReceiveData;
final void Function() onFinished;
Object? error;
bool isFinished = false;
bool _canceled = false;
void cancel() {
_canceled = true;
}
_ImageDownloadWrapper(
this.stream,
this.path,
this.fileBaseName,
this.onReceiveData,
this.onFinished,
) {
listen();
}
void listen() async {
try {
var last = 0;
await for (var progress in stream) {
if(_canceled) {
for (var c in completers) {
c.complete(this);
}
return;
}
onReceiveData(progress.currentBytes - last);
last = progress.currentBytes;
if (progress.finished) {
var data = progress.data ?? await progress.getFile().readAsBytes();
var type = detectFileType(data);
var file = File("$path/$fileBaseName${type.ext}");
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsBytes(data);
isFinished = true;
}
}
} catch (e) {
error = e;
}
if (!isFinished && error == null) {
error = Exception("Failed to download image");
}
onFinished();
for (var c in completers) {
c.complete(this);
}
}
var completers = <Completer<_ImageDownloadWrapper>>[];
Future<_ImageDownloadWrapper> wait() {
if (isFinished) {
return Future.value(this);
}
var completer = Completer<_ImageDownloadWrapper>();
completers.add(completer);
return completer.future;
}
}
这样带来了新的好处: 可以计算下载速度.
储存优化
现在漫画文件夹会被命名为漫画的标题, 而不是ID
内置漫画源的启用状态
在此之前, 对于内置漫画源只能隐藏探索页面, 分类页面, 收藏页面. 现在, 可以彻底禁用某个内置的漫画源.
优化UI
平滑滚动
绝大多数桌面端应用程序都有平滑滚动效果, 而flutter并没有. 实现平滑滚动不是一件困难的事情: 检测到滚轮信号时禁用列表默认的滚动行为, 然后手动处理列表的滚动即可. 需要注意的是, macos有系统实现的平滑滚动, 需要判断App是否运行在macos上.
class SmoothScrollProvider extends StatefulWidget {
const SmoothScrollProvider({super.key, this.controller, required this.builder});
final ScrollController? controller;
final Widget Function(BuildContext, ScrollController, ScrollPhysics) builder;
@override
State<SmoothScrollProvider> createState() => _SmoothScrollProviderState();
}
class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
late final ScrollController _controller;
double? _futurePosition;
static bool _isMouseScroll = App.isDesktop;
@override
void initState() {
_controller = widget.controller ?? ScrollController();
super.initState();
}
@override
Widget build(BuildContext context) {
if(App.isMacOS) {
return widget.builder(
context,
_controller,
const ClampingScrollPhysics(),
);
}
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (event) {
if (_isMouseScroll) {
setState(() {
_isMouseScroll = false;
});
}
},
onPointerSignal: (pointerSignal) {
if (pointerSignal is PointerScrollEvent) {
if (pointerSignal.kind == PointerDeviceKind.mouse &&
!_isMouseScroll) {
setState(() {
_isMouseScroll = true;
});
}
if (!_isMouseScroll) return;
var currentLocation = _controller.position.pixels;
_futurePosition ??= currentLocation;
double k = (_futurePosition! - currentLocation).abs() / 1600 + 1;
_futurePosition =
_futurePosition! + pointerSignal.scrollDelta.dy * k;
_futurePosition = _futurePosition!.clamp(
_controller.position.minScrollExtent,
_controller.position.maxScrollExtent);
_controller.animateTo(_futurePosition!,
duration: _fastAnimationDuration, curve: Curves.linear);
}
},
child: widget.builder(
context,
_controller,
_isMouseScroll
? const NeverScrollableScrollPhysics()
: const ClampingScrollPhysics(),
),
);
}
}
搜索功能优化
现在, tags建议功能只对ehentai和nhentai启用
在搜索结果页面, 切换搜索源功能现在可以切换到自定义漫画源, 添加了搜索选项功能
自定义漫画源
Cloudflare挑战
自定义漫画源现在可以绕过Cloudflare挑战, 此功能是在APP端实现, 不需要在配置文件中写任何代码, APP会自动检测是否触发CF挑战并通过webview绕过
新漫画源
包子漫画已被添加至配置文件仓库, 此漫画源需要至少v4.0.0版本的APP
内置漫画源的功能更新
ehentai评论投票
其它的功能更新
- 设置缓存大小的限制 #580