From 39a834815d8b7ad2709e8594d1ed1b49992bd130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A7=92=E7=A0=82=E7=B3=96?= <90336521+lings03@users.noreply.github.com> Date: Sun, 26 Oct 2025 21:01:52 +0800 Subject: [PATCH 1/2] Optimize favorite page. --- lib/components/comic.dart | 83 +++++++------ lib/components/scroll.dart | 110 +++++++++++++----- lib/pages/favorites/local_favorites_page.dart | 16 ++- lib/pages/history_page.dart | 2 +- lib/pages/local_comics_page.dart | 4 +- 5 files changed, 136 insertions(+), 79 deletions(-) diff --git a/lib/components/comic.dart b/lib/components/comic.dart index 6b8a723..f73ac9b 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -753,9 +753,9 @@ class SliverGridComics extends StatefulWidget { final List Function(Comic)? menuBuilder; - final void Function(Comic)? onTap; + final void Function(Comic, int heroID)? onTap; - final void Function(Comic)? onLongPressed; + final void Function(Comic, int heroID)? onLongPressed; @override State createState() => _SliverGridComicsState(); @@ -856,52 +856,51 @@ class _SliverGridComics extends StatelessWidget { final List Function(Comic)? menuBuilder; - final void Function(Comic)? onTap; + final void Function(Comic, int heroID)? onTap; - final void Function(Comic)? onLongPressed; + final void Function(Comic, int heroID)? onLongPressed; @override Widget build(BuildContext context) { return SliverGrid( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == comics.length - 1) { - onLastItemBuild?.call(); - } - var badge = badgeBuilder?.call(comics[index]); - var isSelected = - selection == null ? false : selection![comics[index]] ?? false; - var comic = ComicTile( - comic: comics[index], - badge: badge, - menuOptions: menuBuilder?.call(comics[index]), - onTap: onTap != null ? () => onTap!(comics[index]) : null, - onLongPressed: onLongPressed != null - ? () => onLongPressed!(comics[index]) + delegate: SliverChildBuilderDelegate((context, index) { + if (index == comics.length - 1) { + onLastItemBuild?.call(); + } + var badge = badgeBuilder?.call(comics[index]); + var isSelected = selection == null + ? false + : selection![comics[index]] ?? false; + var comic = ComicTile( + comic: comics[index], + badge: badge, + menuOptions: menuBuilder?.call(comics[index]), + onTap: onTap != null + ? () => onTap!(comics[index], heroIDs[index]) + : null, + onLongPressed: onLongPressed != null + ? () => onLongPressed!(comics[index], heroIDs[index]) + : null, + heroID: heroIDs[index], + ); + if (selection == null) { + return comic; + } + return AnimatedContainer( + key: ValueKey(comics[index].id), + duration: const Duration(milliseconds: 150), + decoration: BoxDecoration( + color: isSelected + ? Theme.of( + context, + ).colorScheme.secondaryContainer.toOpacity(0.72) : null, - heroID: heroIDs[index], - ); - if (selection == null) { - return comic; - } - return AnimatedContainer( - key: ValueKey(comics[index].id), - duration: const Duration(milliseconds: 150), - decoration: BoxDecoration( - color: isSelected - ? Theme.of(context) - .colorScheme - .secondaryContainer - .toOpacity(0.72) - : null, - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.all(4), - child: comic, - ); - }, - childCount: comics.length, - ), + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(4), + child: comic, + ); + }, childCount: comics.length), gridDelegate: SliverGridDelegateWithComics(), ); } diff --git a/lib/components/scroll.dart b/lib/components/scroll.dart index f42fac3..4c2d6f4 100644 --- a/lib/components/scroll.dart +++ b/lib/components/scroll.dart @@ -241,6 +241,10 @@ class _AppScrollBarState extends State { late final VerticalDragGestureRecognizer _dragGestureRecognizer; + bool _isVisible = false; + Timer? _hideTimer; + static const _hideDuration = Duration(seconds: 2); + @override void initState() { super.initState(); @@ -248,7 +252,41 @@ class _AppScrollBarState extends State { _scrollController.addListener(onChanged); Future.microtask(onChanged); _dragGestureRecognizer = VerticalDragGestureRecognizer() - ..onUpdate = onUpdate; + ..onUpdate = onUpdate + ..onStart = (_) { + _showScrollbar(); + } + ..onEnd = (_) { + _scheduleHide(); + }; + } + + @override + void dispose() { + _hideTimer?.cancel(); + _scrollController.removeListener(onChanged); + _dragGestureRecognizer.dispose(); + super.dispose(); + } + + void _showScrollbar() { + if (!_isVisible && mounted) { + setState(() { + _isVisible = true; + }); + } + _hideTimer?.cancel(); + } + + void _scheduleHide() { + _hideTimer?.cancel(); + _hideTimer = Timer(_hideDuration, () { + if (mounted && _isVisible) { + setState(() { + _isVisible = false; + }); + } + }); } void onUpdate(DragUpdateDetails details) { @@ -269,14 +307,24 @@ class _AppScrollBarState extends State { void onChanged() { if (_scrollController.positions.isEmpty) return; var position = _scrollController.position; + + bool hasChanged = false; if (position.minScrollExtent != minExtent || position.maxScrollExtent != maxExtent || position.pixels != this.position) { - setState(() { - minExtent = position.minScrollExtent; - maxExtent = position.maxScrollExtent; - this.position = position.pixels; - }); + hasChanged = true; + minExtent = position.minScrollExtent; + maxExtent = position.maxScrollExtent; + this.position = position.pixels; + } + + if (hasChanged) { + _showScrollbar(); + _scheduleHide(); + } + + if (hasChanged && mounted) { + setState(() {}); } } @@ -300,29 +348,35 @@ class _AppScrollBarState extends State { Positioned( top: top + widget.topPadding, right: 0, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: Listener( - behavior: HitTestBehavior.translucent, - onPointerDown: (event) { - _dragGestureRecognizer.addPointer(event); - }, - child: SizedBox( - width: _scrollIndicatorSize/2, - height: _scrollIndicatorSize, - child: CustomPaint( - painter: _ScrollIndicatorPainter( - backgroundColor: context.colorScheme.surface, - shadowColor: context.colorScheme.shadow, + child: AnimatedOpacity( + opacity: _isVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 200), + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) => _showScrollbar(), + onExit: (_) => _scheduleHide(), + child: Listener( + behavior: HitTestBehavior.translucent, + onPointerDown: (event) { + _dragGestureRecognizer.addPointer(event); + }, + child: SizedBox( + width: _scrollIndicatorSize / 2, + height: _scrollIndicatorSize, + child: CustomPaint( + painter: _ScrollIndicatorPainter( + backgroundColor: context.colorScheme.surface, + shadowColor: context.colorScheme.shadow, + ), + child: Column( + children: [ + const Spacer(), + Icon(Icons.arrow_drop_up, size: 18), + Icon(Icons.arrow_drop_down, size: 18), + const Spacer(), + ], + ).paddingLeft(4), ), - child: Column( - children: [ - const Spacer(), - Icon(Icons.arrow_drop_up, size: 18), - Icon(Icons.arrow_drop_down, size: 18), - const Spacer(), - ], - ).paddingLeft(4), ), ), ), diff --git a/lib/pages/favorites/local_favorites_page.dart b/lib/pages/favorites/local_favorites_page.dart index 3002158..686ec77 100644 --- a/lib/pages/favorites/local_favorites_page.dart +++ b/lib/pages/favorites/local_favorites_page.dart @@ -627,7 +627,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { ), ]; }, - onTap: (c) { + onTap: (c, heroID) { if (multiSelectMode) { setState(() { if (selectedComics.containsKey(c as FavoriteItem)) { @@ -639,18 +639,22 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> { lastSelectedIndex = comics.indexOf(c); }); } else if (appdata.settings["onClickFavorite"] == "viewDetail") { - App.mainNavigatorKey?.currentContext - ?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey)); - } else { App.mainNavigatorKey?.currentContext?.to( - () => ReaderWithLoading( + () => ComicPage( id: c.id, sourceKey: c.sourceKey, + cover: c.cover, + title: c.title, + heroID: heroID, ), ); + } else { + App.mainNavigatorKey?.currentContext?.to( + () => ReaderWithLoading(id: c.id, sourceKey: c.sourceKey), + ); } }, - onLongPressed: (c) { + onLongPressed: (c, heroID) { setState(() { if (!multiSelectMode) { multiSelectMode = true; diff --git a/lib/pages/history_page.dart b/lib/pages/history_page.dart index bd65ce7..f7d89d8 100644 --- a/lib/pages/history_page.dart +++ b/lib/pages/history_page.dart @@ -211,7 +211,7 @@ class _HistoryPageState extends State { selections: selectedComics, onLongPressed: null, onTap: multiSelectMode - ? (c) { + ? (c, heroID) { setState(() { if (selectedComics.containsKey(c as History)) { selectedComics.remove(c); diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index e4af827..1021010 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -285,13 +285,13 @@ class _LocalComicsPageState extends State { SliverGridComics( comics: comics, selections: selectedComics, - onLongPressed: (c) { + onLongPressed: (c, heroID) { setState(() { multiSelectMode = true; selectedComics[c as LocalComic] = true; }); }, - onTap: (c) { + onTap: (c, heroID) { if (multiSelectMode) { setState(() { if (selectedComics.containsKey(c as LocalComic)) { From df1649def68ceb1886eba632699e0d3d38b8d7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A7=92=E7=A0=82=E7=B3=96?= <90336521+lings03@users.noreply.github.com> Date: Sat, 1 Nov 2025 04:00:21 +0800 Subject: [PATCH 2/2] Home page shared item --- lib/components/comic.dart | 14 +++++++++++++- lib/pages/home_page.dart | 28 ++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/components/comic.dart b/lib/components/comic.dart index f73ac9b..e5840a6 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -1626,7 +1626,7 @@ class _SMClipper extends CustomClipper { class SimpleComicTile extends StatelessWidget { const SimpleComicTile( - {super.key, required this.comic, this.onTap, this.withTitle = false}); + {super.key, required this.comic, this.onTap, this.withTitle = false, this.heroID}); final Comic comic; @@ -1634,6 +1634,8 @@ class SimpleComicTile extends StatelessWidget { final bool withTitle; + final int? heroID; + @override Widget build(BuildContext context) { var image = _findImageProvider(comic); @@ -1659,6 +1661,13 @@ class SimpleComicTile extends StatelessWidget { child: child, ); + if (heroID != null) { + child = Hero( + tag: "cover$heroID", + child: child, + ); + } + child = AnimatedTapRegion( borderRadius: 8, onTap: onTap ?? @@ -1667,6 +1676,9 @@ class SimpleComicTile extends StatelessWidget { () => ComicPage( id: comic.id, sourceKey: comic.sourceKey, + cover: comic.cover, + title: comic.title, + heroID: heroID, ), ); }, diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index c28c1bb..24c4165 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -302,13 +302,18 @@ class _HistoryState extends State<_History> { scrollDirection: Axis.horizontal, itemCount: history.length, itemBuilder: (context, index) { + final heroID = history[index].id.hashCode; return SimpleComicTile( comic: history[index], + heroID: heroID, onTap: () { context.to( () => ComicPage( id: history[index].id, sourceKey: history[index].type.sourceKey, + cover: history[index].cover, + title: history[index].title, + heroID: heroID, ), ); }, @@ -386,7 +391,9 @@ class _LocalState extends State<_Local> { Container( margin: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), + horizontal: 8, + vertical: 2, + ), decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(8), @@ -405,9 +412,22 @@ class _LocalState extends State<_Local> { scrollDirection: Axis.horizontal, itemCount: local.length, itemBuilder: (context, index) { - return SimpleComicTile(comic: local[index]) - .paddingHorizontal(8) - .paddingVertical(2); + final heroID = local[index].id.hashCode; + return SimpleComicTile( + comic: local[index], + heroID: heroID, + onTap: () { + context.to( + () => ComicPage( + id: local[index].id, + sourceKey: local[index].sourceKey, + cover: local[index].cover, + title: local[index].title, + heroID: heroID, + ), + ); + }, + ).paddingHorizontal(8).paddingVertical(2); }, ), ).paddingHorizontal(8),