From f4133eb275ef86ea976c9055912fb0bd947686e4 Mon Sep 17 00:00:00 2001 From: violet-dev Date: Sun, 27 Jul 2025 17:23:53 +0900 Subject: [PATCH 1/5] feat: crop screenshot --- violet/lib/pages/bookmark/crop_bookmark.dart | 97 +++++++++++++------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/violet/lib/pages/bookmark/crop_bookmark.dart b/violet/lib/pages/bookmark/crop_bookmark.dart index efaacca43..bf44b5074 100644 --- a/violet/lib/pages/bookmark/crop_bookmark.dart +++ b/violet/lib/pages/bookmark/crop_bookmark.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:ui'; import 'package:archive/archive_io.dart'; import 'package:collection/collection.dart'; @@ -12,9 +13,9 @@ import 'package:extended_image/extended_image.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pull_down_button/pull_down_button.dart'; import 'package:violet/database/user/bookmark.dart'; @@ -59,6 +60,9 @@ class _CropBookmarkPageState extends State { evictImageUrls(imagesUrlForEvict); } + bool _isCapturing = false; + final GlobalKey _captureKey = GlobalKey(); + @override Widget build(BuildContext context) { final height = MediaQuery.of(context).size.height; @@ -68,19 +72,17 @@ class _CropBookmarkPageState extends State { ? Future.value(widget.bookmarks) : Bookmark.getInstance().then((value) => value.getCropImages()), builder: (context, snapshot) { - if (!snapshot.hasData) { - return Container(); - } - + if (!snapshot.hasData) return Container(); var imgs = snapshot.data!; - if (sortDesc) { - imgs = imgs.reversed.toList(); - } - + if (sortDesc) imgs = imgs.reversed.toList(); imagesUrlForEvict = List.filled(imgs.length, ''); - return MasonryGridView.count( - physics: const BouncingScrollPhysics(), + final masonryGrid = MasonryGridView.count( + key: _isCapturing ? null : PageStorageKey('lazyGrid'), // 스크롤 위치 유지 + physics: _isCapturing + ? const NeverScrollableScrollPhysics() + : const BouncingScrollPhysics(), + shrinkWrap: _isCapturing, // 전체 렌더링 여부 crossAxisCount: columnCount.value, mainAxisSpacing: 6.0 / columnCount.value, crossAxisSpacing: 6.0 / columnCount.value, @@ -96,36 +98,46 @@ class _CropBookmarkPageState extends State { index, e.article(), e.page(), - Rect.fromLTRB( - area[0], - area[1], - area[2], - area[3], - ), + Rect.fromLTRB(area[0], area[1], area[2], area[3]), e.aspectRatio(), ); }, ); + + if (_isCapturing) { + return SingleChildScrollView( + child: RepaintBoundary( + key: _captureKey, + child: masonryGrid, + ), + ); + } else { + return masonryGrid; + } }, ); return CupertinoPageScaffold( child: NestedScrollView( - headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { - return [ - CupertinoSliverNavigationBar( - leading: const CupertinoTheme( - data: CupertinoThemeData(brightness: Brightness.light), - child: Icon(MdiIcons.crop), - ), - largeTitle: const Text('Crop Bookmark'), - trailing: CupertinoTheme( - data: const CupertinoThemeData(brightness: Brightness.light), - child: settingMenu(), - ), + headerSliverBuilder: (context, _) => [ + CupertinoSliverNavigationBar( + largeTitle: const Text('Crop Bookmark'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_isCapturing) + const CupertinoActivityIndicator() + else + CupertinoButton( + padding: EdgeInsets.zero, + onPressed: _startCapture, + child: const Icon(CupertinoIcons.camera), + ), + settingMenu(), + ], ), - ]; - }, + ), + ], body: listView, ), ); @@ -370,6 +382,29 @@ class _CropBookmarkPageState extends State { ), ); } + + void _startCapture() async { + setState(() => _isCapturing = true); + + // 한 프레임 기다렸다가 렌더링 완료 후 캡처 + await Future.delayed(Duration(milliseconds: 10000)); + await Future.delayed(Duration.zero); // flutter 프레임 캐치 + + RenderRepaintBoundary boundary = + _captureKey.currentContext!.findRenderObject() as RenderRepaintBoundary; + + final image = await boundary.toImage(pixelRatio: 3.0); + final byteData = await image.toByteData(format: ImageByteFormat.png); + if (byteData == null) return; + + setState(() => _isCapturing = false); + + final pngBytes = byteData.buffer.asUint8List(); + + final directory = await getApplicationDocumentsDirectory(); + final file = File('${directory.path}/masonry_capture.png'); + await file.writeAsBytes(pngBytes); + } } double calculateCropRawAspectRatio( From 1370f77071e05cd372d0c55d6fe9f8f07843e944 Mon Sep 17 00:00:00 2001 From: violet-dev Date: Sun, 27 Jul 2025 19:16:44 +0900 Subject: [PATCH 2/5] feat: crop bookmark tag filter --- violet/lib/pages/bookmark/crop_bookmark.dart | 39 +++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/violet/lib/pages/bookmark/crop_bookmark.dart b/violet/lib/pages/bookmark/crop_bookmark.dart index bf44b5074..d52e9fc5e 100644 --- a/violet/lib/pages/bookmark/crop_bookmark.dart +++ b/violet/lib/pages/bookmark/crop_bookmark.dart @@ -14,15 +14,19 @@ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; import 'package:pull_down_button/pull_down_button.dart'; +import 'package:violet/database/query.dart'; import 'package:violet/database/user/bookmark.dart'; import 'package:violet/log/log.dart'; import 'package:violet/other/dialogs.dart'; import 'package:violet/pages/common/toast.dart'; import 'package:violet/pages/common/utils.dart'; +import 'package:violet/pages/segment/filter_page.dart'; +import 'package:violet/pages/segment/filter_page_controller.dart'; import 'package:violet/pages/segment/platform_navigator.dart'; import 'package:violet/settings/settings.dart'; import 'package:violet/util/evict_image_urls.dart'; @@ -43,6 +47,9 @@ class _CropBookmarkPageState extends State { final ValueNotifier showOverlay = ValueNotifier(Settings.cropBookmarkShowOverlay.value); bool sortDesc = Settings.cropBookmarkSortDesc.value; + final FilterController _filterController = + FilterController(heroKey: 'cropbookmark'); + List _filterIds = []; List? imagesUrlForEvict; @@ -74,6 +81,9 @@ class _CropBookmarkPageState extends State { builder: (context, snapshot) { if (!snapshot.hasData) return Container(); var imgs = snapshot.data!; + if (_filterIds.isNotEmpty) { + imgs = imgs.where((e) => _filterIds.contains(e.article())).toList(); + } if (sortDesc) imgs = imgs.reversed.toList(); imagesUrlForEvict = List.filled(imgs.length, ''); @@ -125,6 +135,11 @@ class _CropBookmarkPageState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ + CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () => _filter(context), + child: const Icon(MdiIcons.filter), + ), if (_isCapturing) const CupertinoActivityIndicator() else @@ -383,6 +398,28 @@ class _CropBookmarkPageState extends State { ); } + Future _filter(BuildContext context) async { + final bookmarks = await (await Bookmark.getInstance()).getCropImages(); + final ids = bookmarks.map((e) => e.article()).toList(); + final queryResults = await QueryManager.queryIds(ids); + + if (!context.mounted) return; + await PlatformNavigator.navigateSlide( + context, + Provider.value( + value: _filterController, + child: FilterPage( + queryResult: queryResults, + ), + ), + ); + + final filtered = _filterController.applyFilter(queryResults); + setState(() { + _filterIds = filtered.map((e) => e.id()).toList(); + }); + } + void _startCapture() async { setState(() => _isCapturing = true); From 3a7291352d12935e276a0e24e4bc171a8d6f68fa Mon Sep 17 00:00:00 2001 From: violet-dev Date: Sun, 27 Jul 2025 19:30:23 +0900 Subject: [PATCH 3/5] fix: filter for user bookmarks --- violet/lib/pages/bookmark/crop_bookmark.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/violet/lib/pages/bookmark/crop_bookmark.dart b/violet/lib/pages/bookmark/crop_bookmark.dart index d52e9fc5e..5744113fc 100644 --- a/violet/lib/pages/bookmark/crop_bookmark.dart +++ b/violet/lib/pages/bookmark/crop_bookmark.dart @@ -399,7 +399,8 @@ class _CropBookmarkPageState extends State { } Future _filter(BuildContext context) async { - final bookmarks = await (await Bookmark.getInstance()).getCropImages(); + final bookmarks = widget.bookmarks ?? + await (await Bookmark.getInstance()).getCropImages(); final ids = bookmarks.map((e) => e.article()).toList(); final queryResults = await QueryManager.queryIds(ids); From b60885b796813ea1fb4c46055387aa2ea8822b9f Mon Sep 17 00:00:00 2001 From: violet-dev Date: Sun, 27 Jul 2025 19:37:02 +0900 Subject: [PATCH 4/5] ui: adjust filter page tag wrap spacing for windows --- violet/lib/pages/segment/filter_page.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/violet/lib/pages/segment/filter_page.dart b/violet/lib/pages/segment/filter_page.dart index b25e5cc0e..76c9da972 100644 --- a/violet/lib/pages/segment/filter_page.dart +++ b/violet/lib/pages/segment/filter_page.dart @@ -1,6 +1,8 @@ // This source code is a part of Project Violet. // Copyright (C) 2020-2024. violet-team. Licensed under the Apache-2.0 License. +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:html_unescape/html_unescape_small.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -256,10 +258,13 @@ class _FilterPageState extends State { .toList(); } + final spacing = Platform.isWindows ? -7.0 : -7.0; + final runSpacing = Platform.isWindows ? 2.0 : -13.0; + return Wrap( // alignment: WrapAlignment.center, - spacing: -7.0, - runSpacing: -13.0, + spacing: spacing, + runSpacing: runSpacing, children: tags.take(100).map( (element) { return _Chip( From 3554cd8f05328ad28516e9a62af363c2188f29a1 Mon Sep 17 00:00:00 2001 From: violet-dev Date: Mon, 28 Jul 2025 10:24:52 +0900 Subject: [PATCH 5/5] chore: set pixel ratio 10.0 for high scale capture --- violet/lib/pages/bookmark/crop_bookmark.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/violet/lib/pages/bookmark/crop_bookmark.dart b/violet/lib/pages/bookmark/crop_bookmark.dart index 5744113fc..aad92df9d 100644 --- a/violet/lib/pages/bookmark/crop_bookmark.dart +++ b/violet/lib/pages/bookmark/crop_bookmark.dart @@ -431,7 +431,7 @@ class _CropBookmarkPageState extends State { RenderRepaintBoundary boundary = _captureKey.currentContext!.findRenderObject() as RenderRepaintBoundary; - final image = await boundary.toImage(pixelRatio: 3.0); + final image = await boundary.toImage(pixelRatio: 10.0); final byteData = await image.toByteData(format: ImageByteFormat.png); if (byteData == null) return;