From 4337d8b28b00ecbe57671886f0dbf7a343cb4496 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Sat, 4 Apr 2026 22:50:39 +0300 Subject: [PATCH 1/3] fix: improve DialogShell scroll, PageHeader layout, add HideBottomNav widget - DialogShell: replace ListView with SingleChildScrollView for simpler scroll behavior, add ClipRRect to prevent body content overflow on rounded corners - PageHeader: refactor from nested WDiv flex layout to native Flutter Row + Expanded for more predictable sizing, add flex-wrap on actions - HideBottomNav: new InheritedWidget that signals AppLayout to hide bottom navigation for fullscreen routes --- lib/src/ui/widgets/hide_bottom_nav.dart | 28 +++++++ .../widgets/magic_starter_dialog_shell.dart | 74 +++++++++---------- .../ui/widgets/magic_starter_page_header.dart | 37 ++++++---- 3 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 lib/src/ui/widgets/hide_bottom_nav.dart diff --git a/lib/src/ui/widgets/hide_bottom_nav.dart b/lib/src/ui/widgets/hide_bottom_nav.dart new file mode 100644 index 0000000..23d0875 --- /dev/null +++ b/lib/src/ui/widgets/hide_bottom_nav.dart @@ -0,0 +1,28 @@ +import 'package:flutter/widgets.dart'; + +/// Signals to [MagicStarterAppLayout] that the bottom navigation bar +/// should be hidden for routes nested under this widget. +/// +/// ## Usage +/// +/// ```dart +/// MagicRoute.group( +/// layout: (child) => HideBottomNav( +/// child: MagicStarter.view.makeLayout('layout.app', child: child), +/// ), +/// layoutId: 'app.fullscreen', +/// routes: () { ... }, +/// ); +/// ``` +class HideBottomNav extends InheritedWidget { + /// Creates a [HideBottomNav] that hides bottom navigation for its subtree. + const HideBottomNav({super.key, required super.child}); + + /// Returns `true` if a [HideBottomNav] exists above [context]. + static bool of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType() != null; + } + + @override + bool updateShouldNotify(HideBottomNav oldWidget) => false; +} diff --git a/lib/src/ui/widgets/magic_starter_dialog_shell.dart b/lib/src/ui/widgets/magic_starter_dialog_shell.dart index 08166e8..575e21c 100644 --- a/lib/src/ui/widgets/magic_starter_dialog_shell.dart +++ b/lib/src/ui/widgets/magic_starter_dialog_shell.dart @@ -62,50 +62,46 @@ class MagicStarterDialogShell extends StatelessWidget { maxWidth: theme.maxWidth, maxHeight: safeHeight * 0.85, ), - child: WDiv( - className: - '${theme.containerClassName} flex flex-col w-full overflow-hidden', - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (title != null || description != null) - WDiv( - className: theme.headerClassName, - children: [ - if (title != null) - WText( - title!, - className: theme.titleClassName, - ), - if (description != null) - WText( - description!, - className: theme.descriptionClassName, - ), - ], - ), - Flexible( - child: ListView( - shrinkWrap: true, - padding: EdgeInsets.zero, - children: [ - WDiv( + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: WDiv( + className: theme.containerClassName, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (title != null || description != null) + WDiv( + className: theme.headerClassName, + children: [ + if (title != null) + WText(title!, className: theme.titleClassName), + if (description != null) + WText( + description!, + className: theme.descriptionClassName, + ), + ], + ), + Flexible( + child: SingleChildScrollView( + padding: EdgeInsets.zero, + child: WDiv( className: theme.bodyClassName, child: body, ), - ], - ), - ), - if (footerBuilder != null) - Builder( - builder: (dialogContext) => WDiv( - key: const Key('magic_starter_dialog_shell_footer'), - className: theme.footerClassName, - child: footerBuilder!(dialogContext), ), ), - ], + if (footerBuilder != null) + Builder( + builder: (dialogContext) => WDiv( + key: const Key('magic_starter_dialog_shell_footer'), + className: theme.footerClassName, + child: footerBuilder!(dialogContext), + ), + ), + ], + ), ), ), ), diff --git a/lib/src/ui/widgets/magic_starter_page_header.dart b/lib/src/ui/widgets/magic_starter_page_header.dart index a2c4497..ad41c34 100644 --- a/lib/src/ui/widgets/magic_starter_page_header.dart +++ b/lib/src/ui/widgets/magic_starter_page_header.dart @@ -30,15 +30,18 @@ class MagicStarterPageHeader extends StatelessWidget { Widget build(BuildContext context) { final leading = this.leading; return WDiv( - className: - 'w-full flex flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-4 p-2 lg:p-4 border-b border-gray-200 dark:border-gray-700', - children: [ - WDiv( - className: 'flex flex-row items-center gap-3 sm:flex-1 min-w-0', - children: [ - if (leading != null) leading, - WDiv( - className: 'flex flex-col gap-1 flex-1 min-w-0', + className: ''' + w-full p-2 lg:p-4 border-b border-gray-200 dark:border-gray-700 + ''', + child: Row( + children: [ + if (leading != null) ...[ + leading, + const SizedBox(width: 12), + ], + Expanded( + child: WDiv( + className: 'flex flex-col gap-1', children: [ WText( title, @@ -53,14 +56,16 @@ class MagicStarterPageHeader extends StatelessWidget { ), ], ), - ], - ), - if (actions != null && actions!.isNotEmpty) - WDiv( - className: 'flex flex-row items-center gap-2', - children: actions!, ), - ], + if (actions != null && actions!.isNotEmpty) ...[ + const SizedBox(width: 16), + WDiv( + className: 'flex flex-row flex-wrap items-center gap-2', + children: actions!, + ), + ], + ], + ), ); } } From 635af3638d59e2c4f03ee9a3046994f835857886 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Sat, 4 Apr 2026 22:58:46 +0300 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20address=20PR=20#19=20review=20?= =?UTF-8?q?=E2=80=94=20revert=20breaking=20changes,=20rename=20HideBottomN?= =?UTF-8?q?av?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Revert DialogShell to ListView(shrinkWrap: true) + structural classes, removing ClipRRect with hardcoded radius — fixes test expectation and preserves theme compatibility - Revert PageHeader to responsive Wind UI layout (flex-col sm:flex-row), restoring responsive behavior on small screens — fixes test expectation - Rename HideBottomNav → MagicStarterHideBottomNav with proper file naming (magic_starter_hide_bottom_nav.dart), add usage docs showing how to wire into layout --- lib/src/ui/widgets/hide_bottom_nav.dart | 28 ------- .../widgets/magic_starter_dialog_shell.dart | 74 ++++++++++--------- .../magic_starter_hide_bottom_nav.dart | 41 ++++++++++ .../ui/widgets/magic_starter_page_header.dart | 37 ++++------ 4 files changed, 96 insertions(+), 84 deletions(-) delete mode 100644 lib/src/ui/widgets/hide_bottom_nav.dart create mode 100644 lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart diff --git a/lib/src/ui/widgets/hide_bottom_nav.dart b/lib/src/ui/widgets/hide_bottom_nav.dart deleted file mode 100644 index 23d0875..0000000 --- a/lib/src/ui/widgets/hide_bottom_nav.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/widgets.dart'; - -/// Signals to [MagicStarterAppLayout] that the bottom navigation bar -/// should be hidden for routes nested under this widget. -/// -/// ## Usage -/// -/// ```dart -/// MagicRoute.group( -/// layout: (child) => HideBottomNav( -/// child: MagicStarter.view.makeLayout('layout.app', child: child), -/// ), -/// layoutId: 'app.fullscreen', -/// routes: () { ... }, -/// ); -/// ``` -class HideBottomNav extends InheritedWidget { - /// Creates a [HideBottomNav] that hides bottom navigation for its subtree. - const HideBottomNav({super.key, required super.child}); - - /// Returns `true` if a [HideBottomNav] exists above [context]. - static bool of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType() != null; - } - - @override - bool updateShouldNotify(HideBottomNav oldWidget) => false; -} diff --git a/lib/src/ui/widgets/magic_starter_dialog_shell.dart b/lib/src/ui/widgets/magic_starter_dialog_shell.dart index 575e21c..08166e8 100644 --- a/lib/src/ui/widgets/magic_starter_dialog_shell.dart +++ b/lib/src/ui/widgets/magic_starter_dialog_shell.dart @@ -62,46 +62,50 @@ class MagicStarterDialogShell extends StatelessWidget { maxWidth: theme.maxWidth, maxHeight: safeHeight * 0.85, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: WDiv( - className: theme.containerClassName, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (title != null || description != null) - WDiv( - className: theme.headerClassName, - children: [ - if (title != null) - WText(title!, className: theme.titleClassName), - if (description != null) - WText( - description!, - className: theme.descriptionClassName, - ), - ], - ), - Flexible( - child: SingleChildScrollView( - padding: EdgeInsets.zero, - child: WDiv( + child: WDiv( + className: + '${theme.containerClassName} flex flex-col w-full overflow-hidden', + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (title != null || description != null) + WDiv( + className: theme.headerClassName, + children: [ + if (title != null) + WText( + title!, + className: theme.titleClassName, + ), + if (description != null) + WText( + description!, + className: theme.descriptionClassName, + ), + ], + ), + Flexible( + child: ListView( + shrinkWrap: true, + padding: EdgeInsets.zero, + children: [ + WDiv( className: theme.bodyClassName, child: body, ), - ), + ], ), - if (footerBuilder != null) - Builder( - builder: (dialogContext) => WDiv( - key: const Key('magic_starter_dialog_shell_footer'), - className: theme.footerClassName, - child: footerBuilder!(dialogContext), - ), + ), + if (footerBuilder != null) + Builder( + builder: (dialogContext) => WDiv( + key: const Key('magic_starter_dialog_shell_footer'), + className: theme.footerClassName, + child: footerBuilder!(dialogContext), ), - ], - ), + ), + ], ), ), ), diff --git a/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart b/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart new file mode 100644 index 0000000..9e5eac8 --- /dev/null +++ b/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart @@ -0,0 +1,41 @@ +import 'package:flutter/widgets.dart'; + +/// Signals to [MagicStarterAppLayout] that the bottom navigation bar +/// should be hidden for routes nested under this widget. +/// +/// Wrap a layout with this widget to suppress the mobile bottom navigation +/// bar for specific route groups (e.g., fullscreen views, media players, +/// chat screens). +/// +/// ## Usage +/// +/// ```dart +/// MagicRoute.group( +/// layout: (child) => MagicStarterHideBottomNav( +/// child: MagicStarter.view.makeLayout('layout.app', child: child), +/// ), +/// layoutId: 'app.fullscreen', +/// routes: () { ... }, +/// ); +/// ``` +/// +/// In your layout's `build` method, check whether to show bottom nav: +/// +/// ```dart +/// if (!MagicStarterHideBottomNav.of(context)) _buildBottomNav(), +/// ``` +class MagicStarterHideBottomNav extends InheritedWidget { + /// Creates a [MagicStarterHideBottomNav] that hides bottom navigation + /// for its subtree. + const MagicStarterHideBottomNav({super.key, required super.child}); + + /// Returns `true` if a [MagicStarterHideBottomNav] exists above [context]. + static bool of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() != + null; + } + + @override + bool updateShouldNotify(MagicStarterHideBottomNav oldWidget) => false; +} diff --git a/lib/src/ui/widgets/magic_starter_page_header.dart b/lib/src/ui/widgets/magic_starter_page_header.dart index ad41c34..a2c4497 100644 --- a/lib/src/ui/widgets/magic_starter_page_header.dart +++ b/lib/src/ui/widgets/magic_starter_page_header.dart @@ -30,18 +30,15 @@ class MagicStarterPageHeader extends StatelessWidget { Widget build(BuildContext context) { final leading = this.leading; return WDiv( - className: ''' - w-full p-2 lg:p-4 border-b border-gray-200 dark:border-gray-700 - ''', - child: Row( - children: [ - if (leading != null) ...[ - leading, - const SizedBox(width: 12), - ], - Expanded( - child: WDiv( - className: 'flex flex-col gap-1', + className: + 'w-full flex flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-4 p-2 lg:p-4 border-b border-gray-200 dark:border-gray-700', + children: [ + WDiv( + className: 'flex flex-row items-center gap-3 sm:flex-1 min-w-0', + children: [ + if (leading != null) leading, + WDiv( + className: 'flex flex-col gap-1 flex-1 min-w-0', children: [ WText( title, @@ -56,16 +53,14 @@ class MagicStarterPageHeader extends StatelessWidget { ), ], ), - ), - if (actions != null && actions!.isNotEmpty) ...[ - const SizedBox(width: 16), - WDiv( - className: 'flex flex-row flex-wrap items-center gap-2', - children: actions!, - ), ], - ], - ), + ), + if (actions != null && actions!.isNotEmpty) + WDiv( + className: 'flex flex-row items-center gap-2', + children: actions!, + ), + ], ); } } From 9ad623427385fa46f67c483d781d38123cc257af Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Sat, 4 Apr 2026 23:08:23 +0300 Subject: [PATCH 3/3] fix: wire MagicStarterHideBottomNav into AppLayout, export from barrel - AppLayout now checks MagicStarterHideBottomNav.of(context) before rendering bottomNavigationBar on mobile - Export MagicStarterHideBottomNav from magic_starter.dart barrel - Fix docblock: use backtick reference instead of unresolvable bracket link --- lib/magic_starter.dart | 1 + lib/src/ui/layouts/magic_starter_app_layout.dart | 5 ++++- lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/magic_starter.dart b/lib/magic_starter.dart index ad4c96b..cc117c1 100644 --- a/lib/magic_starter.dart +++ b/lib/magic_starter.dart @@ -45,4 +45,5 @@ export 'src/ui/widgets/magic_starter_two_factor_modal.dart'; export 'src/ui/widgets/magic_starter_timezone_select.dart'; export 'src/ui/widgets/magic_starter_page_header.dart'; export 'src/ui/widgets/magic_starter_dialog_shell.dart'; +export 'src/ui/widgets/magic_starter_hide_bottom_nav.dart'; export 'src/ui/views/teams/magic_starter_team_invitation_accept_view.dart'; diff --git a/lib/src/ui/layouts/magic_starter_app_layout.dart b/lib/src/ui/layouts/magic_starter_app_layout.dart index 8bf74a4..8c226e3 100644 --- a/lib/src/ui/layouts/magic_starter_app_layout.dart +++ b/lib/src/ui/layouts/magic_starter_app_layout.dart @@ -8,6 +8,7 @@ import '../../magic_starter_manager.dart'; import '../widgets/magic_starter_team_selector.dart'; import '../widgets/magic_starter_user_profile_dropdown.dart'; import 'package:magic_notifications/magic_notifications.dart'; +import '../widgets/magic_starter_hide_bottom_nav.dart'; import '../widgets/magic_starter_notification_dropdown.dart'; /// Default App Layout for Magic Starter. @@ -132,7 +133,9 @@ class _MagicStarterAppLayoutState extends State { ], ), ), - bottomNavigationBar: (!isDesktop && hasBottomNav) + bottomNavigationBar: (!isDesktop && + hasBottomNav && + !MagicStarterHideBottomNav.of(context)) ? _buildBottomNav(context, currentPath) : null, ); diff --git a/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart b/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart index 9e5eac8..d6caee1 100644 --- a/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart +++ b/lib/src/ui/widgets/magic_starter_hide_bottom_nav.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; -/// Signals to [MagicStarterAppLayout] that the bottom navigation bar +/// Signals to `MagicStarterAppLayout` that the bottom navigation bar /// should be hidden for routes nested under this widget. /// /// Wrap a layout with this widget to suppress the mobile bottom navigation