avatar nyne
Open App

Pica Comic v4.0.0

#开发

重构代码结构

v4.0.0的第一个commit为重构代码结构, 这不会对App的功能产生影响, 但能让项目更优雅

具体而言, 有以下的变化:

关键词屏蔽

v4.0.0之前, 关键词屏蔽是在网络请求时进行, 这导致对于每个漫画源都需要实现一次关键词屏蔽的逻辑.

现在关键词屏蔽在页面构建时进行, 这带来了两个好处:

可见的屏蔽

设置-浏览中新添加了一个选项: 完全隐藏屏蔽的作品, 默认值启用, 当禁用时会有这样的效果:

image

快速屏蔽

漫画块的右键菜单和长按菜单中添加了新的选项: 屏蔽

image

下载功能改进

改进下载逻辑

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;
  }
}

这样带来了新的好处: 可以计算下载速度.

image

储存优化

现在漫画文件夹会被命名为漫画的标题, 而不是ID

内置漫画源的启用状态

在此之前, 对于内置漫画源只能隐藏探索页面, 分类页面, 收藏页面. 现在, 可以彻底禁用某个内置的漫画源.

image

优化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启用

在搜索结果页面, 切换搜索源功能现在可以切换到自定义漫画源, 添加了搜索选项功能

image image

自定义漫画源

Cloudflare挑战

自定义漫画源现在可以绕过Cloudflare挑战, 此功能是在APP端实现, 不需要在配置文件中写任何代码, APP会自动检测是否触发CF挑战并通过webview绕过

新漫画源

包子漫画已被添加至配置文件仓库, 此漫画源需要至少v4.0.0版本的APP

内置漫画源的功能更新

ehentai评论投票

#600

image

其它的功能更新

错误修复