diff --git a/lib/database/user/bookmark.dart b/lib/database/user/bookmark.dart index a6299387a..304799ae5 100644 --- a/lib/database/user/bookmark.dart +++ b/lib/database/user/bookmark.dart @@ -127,6 +127,13 @@ class BookmarkCropImage { var db = await CommonUserDatabase.getInstance(); await db.update('BookmarkCropImage', result, 'Id=?', [id()]); } + + Map toJson() => { + 'article': article(), + 'page': page(), + 'aspectRatio': aspectRatio(), + 'area': area(), + }; } class Bookmark { diff --git a/lib/pages/article_info/preview_area.dart b/lib/pages/article_info/preview_area.dart index 1abe20ba4..9af638726 100644 --- a/lib/pages/article_info/preview_area.dart +++ b/lib/pages/article_info/preview_area.dart @@ -27,6 +27,14 @@ class PreviewAreaWidget extends StatelessWidget { @override Widget build(BuildContext context) { + var columnLength = 3; + + if (MediaQuery.of(context).orientation == Orientation.landscape) { + columnLength = 6; + } else { + columnLength = 3; + } + if (ProviderManager.isExists(queryResult.id())) { return FutureBuilder( future: Future.value(1).then((value) async { @@ -48,7 +56,7 @@ class PreviewAreaWidget extends StatelessWidget { controller: null, physics: const ScrollPhysics(), shrinkWrap: true, - crossAxisCount: 3, + crossAxisCount: columnLength, childAspectRatio: 3 / 4, crossAxisSpacing: 8, mainAxisSpacing: 8, diff --git a/lib/pages/bookmark/crop_bookmark.dart b/lib/pages/bookmark/crop_bookmark.dart index ebd3ac51c..a1229990e 100644 --- a/lib/pages/bookmark/crop_bookmark.dart +++ b/lib/pages/bookmark/crop_bookmark.dart @@ -2,11 +2,13 @@ // Copyright (C) 2020-2024. violet-team. Licensed under the Apache-2.0 License. import 'dart:async'; +import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:provider/provider.dart'; import 'package:pull_down_button/pull_down_button.dart'; @@ -15,6 +17,7 @@ import 'package:violet/component/hentai.dart'; import 'package:violet/component/image_provider.dart'; import 'package:violet/database/user/bookmark.dart'; import 'package:violet/database/user/record.dart'; +import 'package:violet/log/log.dart'; import 'package:violet/other/dialogs.dart'; import 'package:violet/pages/common/utils.dart'; import 'package:violet/pages/viewer/viewer_page.dart'; @@ -24,6 +27,7 @@ import 'package:violet/settings/settings.dart'; import 'package:violet/util/evict_image_urls.dart'; import 'package:violet/widgets/article_item/image_provider_manager.dart'; import 'package:violet/widgets/cupertino_switch_list_tile.dart'; +import 'package:violet/widgets/toast.dart'; import 'package:violet/widgets/v_cached_network_image.dart'; class CropBookmarkPage extends StatefulWidget { @@ -65,8 +69,8 @@ class _CropBookmarkPageState extends State { return MasonryGridView.count( physics: const BouncingScrollPhysics(), crossAxisCount: columnCount.value, - mainAxisSpacing: 4, - crossAxisSpacing: 4, + mainAxisSpacing: 6.0 / columnCount.value, + crossAxisSpacing: 6.0 / columnCount.value, itemCount: imgs.length, cacheExtent: height * 3.0, padding: EdgeInsets.zero, @@ -143,9 +147,9 @@ class _CropBookmarkPageState extends State { }), builder: (context, AsyncSnapshot>> snapshot) { - final width = - (MediaQuery.of(context).size.width - 4 * (columnCount.value - 1)) / - columnCount.value; + final width = (MediaQuery.of(context).size.width - + (6.0 / columnCount.value) * (columnCount.value - 1)) / + columnCount.value; final cropRawAspectRatio = calculateCropRawAspectRatio(width, aspectRatio, rect); @@ -253,14 +257,75 @@ class _CropBookmarkPageState extends State { PullDownMenuActionsRow.medium( items: [ PullDownMenuItem( - onTap: () {}, title: 'Export', icon: CupertinoIcons.arrowshape_turn_up_left, + onTap: () async { + final crops = + await (await Bookmark.getInstance()).getCropImages(); + await showOkDialog( + context, jsonEncode(crops), 'Export Crop Bookmarks'); + }, ), PullDownMenuItem( - onTap: () {}, title: 'Import', icon: CupertinoIcons.square_arrow_down, + onTap: () async { + final text = TextEditingController(); + if (await showOkCancelDialog( + titleText: 'Import Crop Bookmarks', + context: context, + contentPadding: const EdgeInsets.fromLTRB(12, 0, 12, 0), + contentBuilder: (_) => TextField( + controller: text, + autofocus: true, + minLines: 3, + maxLines: 999, + ), + )) { + try { + var arr = jsonDecode(text.text); + if (arr is Map) { + arr = [arr]; + } + + final bookmark = await Bookmark.getInstance(); + + for (var e in arr as List) { + final elem = e as Map; + await bookmark.insertCropImage(elem['article'], + elem['page'], elem['area'], elem['aspectRatio']); + } + + FToast ftoast = FToast(); + ftoast.init(context); + ftoast.showToast( + child: const ToastWrapper( + isCheck: true, + isWarning: false, + msg: 'Successful Importing!', + ), + ignorePointer: true, + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 4), + ); + setState(() {}); + } catch (e) { + FToast ftoast = FToast(); + ftoast.init(context); + ftoast.showToast( + child: const ToastWrapper( + isCheck: false, + isWarning: false, + msg: 'Import Error! Check Log!', + ), + ignorePointer: true, + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 4), + ); + Logger.error('[Import Crop] $e'); + } + } + }, ), ], ), @@ -343,9 +408,9 @@ class _CropImageWidgetState extends State { @override Widget build(BuildContext context) { - final width = - (MediaQuery.of(context).size.width - 4 * (widget.columnCount - 1)) / - widget.columnCount; + final width = (MediaQuery.of(context).size.width - + (6.0 / widget.columnCount) * (widget.columnCount - 1)) / + widget.columnCount; final height = width / widget.aspectRatio; // 참고: https://github.com/project-violet/violet/pull/363#issuecomment-1908442196 diff --git a/lib/pages/viewer/overlay/viewer_tab_panel.dart b/lib/pages/viewer/overlay/viewer_tab_panel.dart index 742ec25e4..b7909ea5f 100644 --- a/lib/pages/viewer/overlay/viewer_tab_panel.dart +++ b/lib/pages/viewer/overlay/viewer_tab_panel.dart @@ -44,9 +44,6 @@ class ViewerTabPanel extends StatefulWidget { class _ViewerTabPanelState extends State { final PageController _pageController = PageController(initialPage: 0); - // static const _kDuration = const Duration(milliseconds: 300); - // static const _kCurve = Curves.ease; - static const _kDuration = Duration(milliseconds: 300); static const _kCurve = Curves.ease; @@ -147,6 +144,19 @@ class __UsableTabListState extends State<_UsableTabList> final ScrollController _scrollController = ScrollController(); Map itemKeys = {}; + var columnLength = 3; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (MediaQuery.of(context).orientation == Orientation.landscape) { + columnLength = 6; + } else { + columnLength = 3; + } + } + @override void initState() { super.initState(); @@ -158,7 +168,7 @@ class __UsableTabListState extends State<_UsableTabList> Future.value(1).then((value) { var row = widget.usableTabList .indexWhere((element) => element.id() == widget.articleId) ~/ - 3; + columnLength; if (row == 0) return; var firstItemHeight = (itemKeys[widget.usableTabList.first.id()]! .currentContext! @@ -182,8 +192,8 @@ class __UsableTabListState extends State<_UsableTabList> SliverPadding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), sliver: SliverGrid( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columnLength, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 3 / 4, @@ -201,7 +211,7 @@ class __UsableTabListState extends State<_UsableTabList> queryResult: e, addBottomPadding: false, showDetail: false, - width: (windowWidth - 4.0) / 3.0, + width: (windowWidth - 4.0) / columnLength, thumbnailTag: const Uuid().v4(), selectMode: true, selectCallback: () { @@ -246,6 +256,19 @@ class __ArtistsArticleTabListState extends State<_ArtistsArticleTabList> bool isJumped = false; List articleList = []; + var columnLength = 3; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (MediaQuery.of(context).orientation == Orientation.landscape) { + columnLength = 6; + } else { + columnLength = 3; + } + } + @override void initState() { super.initState(); @@ -305,7 +328,7 @@ class __ArtistsArticleTabListState extends State<_ArtistsArticleTabList> Future.delayed(const Duration(milliseconds: 50)).then((value) { var row = articleList .indexWhere((element) => element.id() == widget.articleId) ~/ - 3; + columnLength; if (row == 0) return; var firstItemHeight = (itemKeys[articleList.first.id()]! .currentContext! @@ -342,8 +365,8 @@ class __ArtistsArticleTabListState extends State<_ArtistsArticleTabList> SliverPadding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), sliver: SliverGrid( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columnLength, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 3 / 4, @@ -361,7 +384,7 @@ class __ArtistsArticleTabListState extends State<_ArtistsArticleTabList> queryResult: e, addBottomPadding: false, showDetail: false, - width: (windowWidth - 4.0) / 3.0, + width: (windowWidth - 4.0) / columnLength, thumbnailTag: const Uuid().v4(), selectMode: true, selectCallback: () async { diff --git a/lib/pages/viewer/overlay/viewer_thumbnails.dart b/lib/pages/viewer/overlay/viewer_thumbnails.dart index 65d214bc0..753bf84d1 100644 --- a/lib/pages/viewer/overlay/viewer_thumbnails.dart +++ b/lib/pages/viewer/overlay/viewer_thumbnails.dart @@ -33,6 +33,8 @@ class _ViewerThumbnailState extends State { List itemKeys = []; final ScrollController _scrollController = ScrollController(); + var columnLength = 3; + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -42,6 +44,12 @@ class _ViewerThumbnailState extends State { Iterable.generate(_pageInfo.uris.length, (index) => GlobalKey()), ); + if (MediaQuery.of(context).orientation == Orientation.landscape) { + columnLength = 6; + } else { + columnLength = 3; + } + if (_pageInfo.useFileSystem) _jumpToViewedPage(); } @@ -51,7 +59,7 @@ class _ViewerThumbnailState extends State { if (_alreadyJumped) return; _alreadyJumped = true; Future.value(1).then((value) { - var row = widget.viewedPage ~/ 3; + var row = widget.viewedPage ~/ columnLength; if (row == 0) return; var firstItemHeight = (itemKeys[0].currentContext!.findRenderObject() as RenderBox) @@ -105,7 +113,7 @@ class _ViewerThumbnailState extends State { controller: _scrollController, physics: const BouncingScrollPhysics(), shrinkWrap: true, - crossAxisCount: 3, + crossAxisCount: columnLength, childAspectRatio: 3 / 4, crossAxisSpacing: 8, mainAxisSpacing: 8, @@ -166,7 +174,7 @@ class _ViewerThumbnailState extends State { controller: _scrollController, physics: const BouncingScrollPhysics(), shrinkWrap: true, - crossAxisCount: 3, + crossAxisCount: columnLength, childAspectRatio: 3 / 4, crossAxisSpacing: 8, mainAxisSpacing: 8,