fujidaiti / smooth_sheets

Sheet widgets with smooth motion and great flexibility.
https://pub.dev/packages/smooth_sheets
MIT License
195 stars 19 forks source link

Keyboard visibility changes disrupt route transition animation in NavigationSheet #195

Closed kitsuniru closed 3 weeks ago

kitsuniru commented 1 month ago

Hello, steps for reproduction: 1) Create two routes, one route with size 0 and SizedBox, and another route with a full screen widget 2) On the fullscreen route, add a text field and a button to go to the route with size 0 3) Click on the text field, open the keyboard and click on the button to go to zerosize route

Expectation: bottom sheet will close Reality: bottom sheet animation breaks at a random position when the keyboard is closed or opened

fujidaiti commented 1 month ago

Hi @kitsuniru,

one route with size 0

I'm not sure what this means. Can you post some executable code to reproduce the problem?

DJWassink commented 1 month ago

I encountered something similar when navigating while the keyboard is open. For me I have 2 "full screen" pages and navigating while the keyboard is open breaks the transition animation. I managed to record one where the animation is janky (this happens most of the time) and the 2nd attempt it got stuck halfway in the screen (rare).

https://github.com/user-attachments/assets/25e83099-db9a-4f5b-baa9-3618039a3e9e

Reproduce code ```dart import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:smooth_sheets/smooth_sheets.dart'; void main() { // Make the system navigation bar transparent on Android. if (Platform.isAndroid) { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setEnabledSystemUIMode( SystemUiMode.edgeToEdge, overlays: [SystemUiOverlay.top], ).then((_) { SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark.copyWith( systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent, )); }); } runApp(const _AiPlaylistGeneratorExample()); } class _AiPlaylistGeneratorExample extends StatelessWidget { const _AiPlaylistGeneratorExample(); @override Widget build(BuildContext context) { return MaterialApp.router( debugShowCheckedModeBanner: false, routerConfig: router, ); } } final sheetTransitionObserver = NavigationSheetTransitionObserver(); final router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const _Root(), routes: [_sheetShellRoute], ), ], ); // A ShellRoute is used to create a new Navigator for nested navigation in the sheet. final _sheetShellRoute = ShellRoute( observers: [sheetTransitionObserver], pageBuilder: (context, state, navigator) { // Use ModalSheetPage to show a modal sheet. return ModalSheetPage( swipeDismissible: true, child: _SheetShell( navigator: navigator, transitionObserver: sheetTransitionObserver, ), ); }, routes: [_introRoute], ); final _introRoute = GoRoute( path: 'intro', pageBuilder: (context, state) { return const DraggableNavigationSheetPage(child: _IntroPage()); }, routes: [_genreRoute], ); final _genreRoute = GoRoute( path: 'genre', pageBuilder: (context, state) { return const DraggableNavigationSheetPage(child: _SelectGenrePage()); }, // routes: [_moodRoute], ); class _SheetShell extends StatelessWidget { const _SheetShell({ required this.transitionObserver, required this.navigator, }); final NavigationSheetTransitionObserver transitionObserver; final Widget navigator; @override Widget build(BuildContext context) { Future showCancelDialog() { return showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('Are you sure?'), content: const Text('Do you want to cancel the playlist generation?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('Yes'), ), TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('No'), ), ], ); }, ); } return SafeArea( bottom: false, child: PopScope( canPop: false, onPopInvoked: (didPop) async { if (!didPop) { final shouldPop = await showCancelDialog() ?? false; if (shouldPop && context.mounted) { context.go('/'); } } }, child: NavigationSheet( transitionObserver: sheetTransitionObserver, child: Material( // Add circular corners to the sheet. borderRadius: BorderRadius.circular(16), clipBehavior: Clip.antiAlias, color: Theme.of(context).colorScheme.surface, child: navigator, ), ), ), ); } } class _Root extends StatelessWidget { const _Root(); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: () => context.go('/intro'), child: const Text('Generate Playlist'), ), )); } } class _IntroPage extends StatelessWidget { const _IntroPage(); @override Widget build(BuildContext context) { return SheetContentScaffold( appBar: _SharedAppBarHero( appbar: AppBar( leading: IconButton( onPressed: () => context.go('/'), icon: const Icon(Icons.close), ), ), ), body: SafeArea( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 8, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "Hello there!\n" "I'm your AI music assistant. " "Ready to create the perfect playlist for you. 😊", textAlign: TextAlign.center, ), const SizedBox(height: 64), FilledButton( onPressed: () => context.go('/intro/genre'), child: const Text('Continue'), ), const SizedBox(height: 16), TextButton( onPressed: () => context.go('/'), child: const Text('No, thanks'), ), for (final genre in _genres) Chip( label: Text(genre), ), ], ), ), ), ), ); } } class _SelectGenrePage extends StatelessWidget { const _SelectGenrePage(); @override Widget build(BuildContext context) { return SheetContentScaffold( appBar: _SharedAppBarHero(appbar: AppBar()), // Wrap the body in a SingleChildScrollView to prevent // the content from overflowing on small screens. body: SingleChildScrollView( padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 32, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'What genre do you like? (1/3)', ), const SizedBox(height: 24), Wrap( spacing: 10, children: [ const TextField( autofocus: true, decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(16, 12, 12, 12), hintText: 'search', fillColor: Colors.transparent, prefixIcon: ExcludeSemantics(child: Icon(Icons.search)), ), textInputAction: TextInputAction.go, ), for (final genre in _genres) Chip( label: Text(genre), ), ], ), ], ), ), ); } } /// This widget makes it possible to create a (visually) shared appbar across the pages. /// /// For better maintainability, it is recommended to create a page-specific app bar for each page /// instead of a single 'super' shared app bar that includes all the functionality for every page. class _SharedAppBarHero extends StatelessWidget implements PreferredSizeWidget { const _SharedAppBarHero({ required this.appbar, }); final AppBar appbar; @override Size get preferredSize => appbar.preferredSize; @override Widget build(BuildContext context) { return Hero(tag: 'HeroAppBar', child: appbar); } } // ---------------------------------------------------------- // Constants // ---------------------------------------------------------- const _genres = [ 'Pop', 'Rock', 'Hip Hop', 'R&B', 'Country', 'Jazz', 'Classical', 'Electronic', 'Folk', 'Reggae', 'Blues', 'Metal', 'Punk', 'Disco', 'Funk', 'Soul', 'Techno', 'House', 'Ambient', 'Indie', 'Alternative', 'K-Pop', 'Latin', 'Pop', 'Rock', 'Hip Hop', 'R&B', 'Country', 'Jazz', 'Classical', 'Electronic', 'Folk', 'Reggae', 'Blues', 'Metal', 'Punk', 'Disco', 'Funk', 'Soul', 'Techno', 'House', 'Ambient', 'Indie', 'Alternative', 'K-Pop', 'Latin', 'Pop', 'Rock', 'Hip Hop', 'R&B', 'Country', 'Jazz', 'Classical', 'Electronic', 'Folk', 'Reggae', 'Blues', 'Metal', 'Punk', 'Disco', 'Funk', 'Soul', 'Techno', 'House', 'Ambient', 'Indie', 'Alternative', 'K-Pop', 'Latin', ]; ```
fujidaiti commented 1 month ago

Hi, @DJWassink,

Thank you for the report, and seeing the same behavior with the provided code. It seems that the navigation sheet is not properly handling MediaQueryData.viewInsets during page transitions.

NinoBass commented 1 month ago

Any update on this?