Merge pull request #594 from lings03/favorite

Optimize favorite page and home page.
This commit is contained in:
ynyx631
2025-11-01 12:29:42 +08:00
committed by GitHub
6 changed files with 173 additions and 84 deletions

View File

@@ -753,9 +753,9 @@ class SliverGridComics extends StatefulWidget {
final List<MenuEntry> 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<SliverGridComics> createState() => _SliverGridComicsState();
@@ -856,52 +856,51 @@ class _SliverGridComics extends StatelessWidget {
final List<MenuEntry> 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(),
);
}
@@ -1627,7 +1626,7 @@ class _SMClipper extends CustomClipper<Rect> {
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;
@@ -1635,6 +1634,8 @@ class SimpleComicTile extends StatelessWidget {
final bool withTitle;
final int? heroID;
@override
Widget build(BuildContext context) {
var image = _findImageProvider(comic);
@@ -1660,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 ??
@@ -1668,6 +1676,9 @@ class SimpleComicTile extends StatelessWidget {
() => ComicPage(
id: comic.id,
sourceKey: comic.sourceKey,
cover: comic.cover,
title: comic.title,
heroID: heroID,
),
);
},

View File

@@ -241,6 +241,10 @@ class _AppScrollBarState extends State<AppScrollBar> {
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<AppScrollBar> {
_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<AppScrollBar> {
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<AppScrollBar> {
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),
),
),
),

View File

@@ -689,7 +689,7 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
),
];
},
onTap: (c) {
onTap: (c, heroID) {
if (multiSelectMode) {
setState(() {
if (selectedComics.containsKey(c as FavoriteItem)) {
@@ -701,20 +701,24 @@ 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,
),
enableIOSGesture: false,
iosFullScreenGesture: false,
);
} else {
App.mainNavigatorKey?.currentContext?.to(
() => ReaderWithLoading(id: c.id, sourceKey: c.sourceKey),
);
}
},
onLongPressed: (c) {
onLongPressed: (c, heroID) {
setState(() {
if (!multiSelectMode) {
multiSelectMode = true;

View File

@@ -211,7 +211,7 @@ class _HistoryPageState extends State<HistoryPage> {
selections: selectedComics,
onLongPressed: null,
onTap: multiSelectMode
? (c) {
? (c, heroID) {
setState(() {
if (selectedComics.containsKey(c as History)) {
selectedComics.remove(c);

View File

@@ -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),

View File

@@ -285,13 +285,13 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
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)) {