From a3e758831bd6a34a92461184aeaaa9ec87a7c7fd Mon Sep 17 00:00:00 2001 From: nyne Date: Wed, 29 Jan 2025 20:52:37 +0800 Subject: [PATCH] Improve animation --- lib/components/page_route.dart | 54 +++++++++ lib/pages/main_page.dart | 209 +++++++++++++++++---------------- 2 files changed, 161 insertions(+), 102 deletions(-) diff --git a/lib/components/page_route.dart b/lib/components/page_route.dart index 5e64131..1657137 100644 --- a/lib/components/page_route.dart +++ b/lib/components/page_route.dart @@ -20,6 +20,7 @@ class AppPageRoute extends PageRoute with _AppRouteTransitionMixin { super.barrierDismissible = false, this.enableIOSGesture = true, this.preventRebuild = true, + this.isRoot = false, }) { assert(opaque); } @@ -44,6 +45,9 @@ class AppPageRoute extends PageRoute with _AppRouteTransitionMixin { @override final bool preventRebuild; + @override + final bool isRoot; + static void updateBackButton() { Future.delayed(const Duration(milliseconds: 300), () { StateController.findOrNull(tag: "back_button")?.update(); @@ -77,6 +81,8 @@ mixin _AppRouteTransitionMixin on PageRoute { Widget? _child; + bool get isRoot; + @override Widget buildPage( BuildContext context, @@ -115,6 +121,22 @@ mixin _AppRouteTransitionMixin on PageRoute { @override Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + if (isRoot) { + return EntrancePageTransition( + animation: CurvedAnimation( + parent: animation, + curve: FluentTheme.of(context).animationCurve, + ), + child: enableIOSGesture && App.isIOS + ? IOSBackGestureDetector( + gestureWidth: _kBackGestureWidth, + enabledCallback: () => _isPopGestureEnabled(this), + onStartPopGesture: () => _startPopGesture(this), + child: child) + : child, + ); + } + return DrillInPageTransition( animation: CurvedAnimation( parent: animation, @@ -388,3 +410,35 @@ class SideBarRoute extends PopupRoute { return IOSBackGestureController(route.controller!, route.navigator!); } } + +class EntrancePageTransition extends StatelessWidget { + /// Creates an entrance page transition + const EntrancePageTransition({ + super.key, + required this.child, + required this.animation, + }); + + /// The widget to be animated + final Widget child; + + /// The animation to drive this transition + final Animation animation; + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: FluentTheme.of(context).micaBackgroundColor, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 0.1), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + ); + } +} diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index e526909..ece1d6e 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -132,112 +132,112 @@ class _MainPageState extends State return DefaultSelectionStyle.merge( selectionColor: FluentTheme.of(context).selectionColor.toOpacity(0.4), child: NavigationView( - appBar: buildAppBar(context, navigatorKey), - pane: NavigationPane( - selected: index, - onChanged: (value) { - setState(() { - index = value; - }); - navigate(value); - }, - items: [ - UserPane(), - PaneItem( - icon: const Icon( - MdIcons.search, - size: 20, - ), - title: Text('Search'.tl), - body: const SizedBox.shrink(), + appBar: buildAppBar(context, navigatorKey), + pane: NavigationPane( + selected: index, + onChanged: (value) { + setState(() { + index = value; + }); + navigate(value); + }, + items: [ + UserPane(), + PaneItem( + icon: const Icon( + MdIcons.search, + size: 20, ), - PaneItem( - icon: const Icon( - MdIcons.downloading, - size: 20, - ), - title: Text('Downloading'.tl), - body: const SizedBox.shrink(), + title: Text('Search'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon( + MdIcons.downloading, + size: 20, ), - PaneItem( - icon: const Icon( - MdIcons.download, - size: 20, - ), - title: Text('Downloaded'.tl), - body: const SizedBox.shrink(), + title: Text('Downloading'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon( + MdIcons.download, + size: 20, ), - PaneItemSeparator(), - PaneItemHeader( - header: Text('${"Illustrations".tl}/${"Manga".tl}') - .paddingBottom(4) - .paddingLeft(8)), - PaneItem( - icon: const Icon( - MdIcons.explore_outlined, - size: 20, - ), - title: Text('Explore'.tl), - body: const SizedBox.shrink(), + title: Text('Downloaded'.tl), + body: const SizedBox.shrink(), + ), + PaneItemSeparator(), + PaneItemHeader( + header: Text('${"Illustrations".tl}/${"Manga".tl}') + .paddingBottom(4) + .paddingLeft(8)), + PaneItem( + icon: const Icon( + MdIcons.explore_outlined, + size: 20, ), - PaneItem( - icon: const Icon(MdIcons.bookmark_outline, size: 20), - title: Text('Bookmarks'.tl), - body: const SizedBox.shrink(), - ), - PaneItem( - icon: const Icon(MdIcons.interests_outlined, size: 20), - title: Text('Following'.tl), - body: const SizedBox.shrink(), - ), - PaneItem( - icon: const Icon(MdIcons.history, size: 20), - title: Text('History'.tl), - body: const SizedBox.shrink(), - ), - PaneItem( - icon: const Icon(MdIcons.leaderboard_outlined, size: 20), - title: Text('Ranking'.tl), - body: const SizedBox.shrink(), - ), - PaneItemSeparator(), - PaneItemHeader( - header: Text("Novel".tl).paddingBottom(4).paddingLeft(8)), - PaneItem( - icon: const Icon(MdIcons.featured_play_list_outlined, size: 20), - title: Text('Recommendation'.tl), - body: const SizedBox.shrink(), - ), - PaneItem( - icon: - const Icon(MdIcons.collections_bookmark_outlined, size: 20), - title: Text('Bookmarks'.tl), - body: const SizedBox.shrink(), - ), - PaneItem( - icon: const Icon(MdIcons.leaderboard_outlined, size: 20), - title: Text('Ranking'.tl), - body: const SizedBox.shrink(), - ), - PaneItemSeparator(), - PaneItem( - icon: const Icon(MdIcons.settings_outlined, size: 20), - title: Text('Settings'.tl), - body: const SizedBox.shrink(), - ), - ], + title: Text('Explore'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.bookmark_outline, size: 20), + title: Text('Bookmarks'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.interests_outlined, size: 20), + title: Text('Following'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.history, size: 20), + title: Text('History'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.leaderboard_outlined, size: 20), + title: Text('Ranking'.tl), + body: const SizedBox.shrink(), + ), + PaneItemSeparator(), + PaneItemHeader( + header: Text("Novel".tl).paddingBottom(4).paddingLeft(8)), + PaneItem( + icon: const Icon(MdIcons.featured_play_list_outlined, size: 20), + title: Text('Recommendation'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.collections_bookmark_outlined, size: 20), + title: Text('Bookmarks'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.leaderboard_outlined, size: 20), + title: Text('Ranking'.tl), + body: const SizedBox.shrink(), + ), + PaneItemSeparator(), + PaneItem( + icon: const Icon(MdIcons.settings_outlined, size: 20), + title: Text('Settings'.tl), + body: const SizedBox.shrink(), + ), + ], + ), + paneBodyBuilder: (pane, child) => MediaQuery.removePadding( + context: context, + removeTop: true, + child: Navigator( + key: navigatorKey, + onGenerateRoute: (settings) => AppPageRoute( + isRoot: true, + builder: (context) => pageBuilders.elementAtOrNull(index)!(), + ), ), - paneBodyBuilder: (pane, child) => MediaQuery.removePadding( - context: context, - removeTop: true, - child: Navigator( - key: navigatorKey, - onGenerateRoute: (settings) => AppPageRoute( - builder: (context) => - pageBuilders.elementAtOrNull(index)!(), - ), - ), - )), + ), + ), ); } @@ -263,7 +263,12 @@ class _MainPageState extends State child: Text("Invalid Page: $index"), ); navigatorKey.currentState!.pushAndRemoveUntil( - AppPageRoute(builder: (context) => page()), (route) => false); + AppPageRoute( + builder: (context) => page(), + isRoot: true, + ), + (route) => false, + ); } NavigationAppBar buildAppBar(