[flutter_adaptive_scaffold] Support RTL (#4204)
This PR removes the hardcoded rtl directionality placed on top of `AdaptiveScaffold` for an unknown reason.
| RTL | LTR |
|-----|-----|
|  |  |
## Changes in this PR
- Core: Removed this hardcoded `Directionality` widget https://github.com/flutter/packages/blob/050729760b251e315af01876cc7d7de5dcfba0e9/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart#L491-L494
- Core: Added tests to ensure text direction is passed correctly
- Core: Modified `example/adaptive_scaffold_demo.dart` to demonstrate support for RTL
- Side: There were some tests that were getting skipped, I have enabled them since their related issues landed on stable.
- Side: Stopped ignoring `prefer_const_constructors` since it landed in stable as well.
I made the commit messages as descriptive as possible so that it's easier to review relevant changes.
## Related issues
- Supersedes #3602
- Fixes https://github.com/flutter/flutter/issues/119661
Question for reviewers: should the next version be 0.1.5 or 0.2.0 ?
diff --git a/packages/flutter_adaptive_scaffold/CHANGELOG.md b/packages/flutter_adaptive_scaffold/CHANGELOG.md
index 0c90756..857f82f 100644
--- a/packages/flutter_adaptive_scaffold/CHANGELOG.md
+++ b/packages/flutter_adaptive_scaffold/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.1.5
+* Added support for Right-to-left (RTL) directionality.
* Fixes stale ignore: prefer_const_constructors.
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md
index 612e62c..e9324af 100644
--- a/packages/flutter_adaptive_scaffold/README.md
+++ b/packages/flutter_adaptive_scaffold/README.md
@@ -137,110 +137,108 @@
<?code-excerpt "adaptive_layout_demo.dart (Example)"?>
```dart
- // AdaptiveLayout has a number of slots that take SlotLayouts and these
- // SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs.
- return AdaptiveLayout(
- // Primary navigation config has nothing from 0 to 600 dp screen width,
- // then an unextended NavigationRail with no labels and just icons then an
- // extended NavigationRail with both icons and labels.
- primaryNavigation: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig>{
- Breakpoints.medium: SlotLayout.from(
- inAnimation: AdaptiveScaffold.leftOutIn,
- key: const Key('Primary Navigation Medium'),
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- selectedIndex: selectedNavigation,
- onDestinationSelected: (int newIndex) {
- setState(() {
- selectedNavigation = newIndex;
- });
- },
- leading: const Icon(Icons.menu),
- destinations: destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
- ),
- ),
- Breakpoints.large: SlotLayout.from(
- key: const Key('Primary Navigation Large'),
- inAnimation: AdaptiveScaffold.leftOutIn,
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- selectedIndex: selectedNavigation,
- onDestinationSelected: (int newIndex) {
- setState(() {
- selectedNavigation = newIndex;
- });
- },
- extended: true,
- leading: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: const <Widget>[
- Text(
- 'REPLY',
- style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
- ),
- Icon(Icons.menu_open)
- ],
+// AdaptiveLayout has a number of slots that take SlotLayouts and these
+// SlotLayouts' configs take maps of Breakpoints to SlotLayoutConfigs.
+return AdaptiveLayout(
+ // Primary navigation config has nothing from 0 to 600 dp screen width,
+ // then an unextended NavigationRail with no labels and just icons then an
+ // extended NavigationRail with both icons and labels.
+ primaryNavigation: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig>{
+ Breakpoints.medium: SlotLayout.from(
+ inAnimation: AdaptiveScaffold.leftOutIn,
+ key: const Key('Primary Navigation Medium'),
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ selectedIndex: selectedNavigation,
+ onDestinationSelected: (int newIndex) {
+ setState(() {
+ selectedNavigation = newIndex;
+ });
+ },
+ leading: const Icon(Icons.menu),
+ destinations: destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
+ ),
+ ),
+ Breakpoints.large: SlotLayout.from(
+ key: const Key('Primary Navigation Large'),
+ inAnimation: AdaptiveScaffold.leftOutIn,
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ selectedIndex: selectedNavigation,
+ onDestinationSelected: (int newIndex) {
+ setState(() {
+ selectedNavigation = newIndex;
+ });
+ },
+ extended: true,
+ leading: const Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: <Widget>[
+ Text(
+ 'REPLY',
+ style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
),
- destinations: destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- trailing: trailingNavRail,
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
- ),
+ Icon(Icons.menu_open)
+ ],
),
- },
+ destinations: destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ trailing: trailingNavRail,
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
+ ),
),
- // Body switches between a ListView and a GridView from small to medium
- // breakpoints and onwards.
- body: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig>{
- Breakpoints.small: SlotLayout.from(
- key: const Key('Body Small'),
- builder: (_) => ListView.builder(
- itemCount: children.length,
- itemBuilder: (BuildContext context, int index) => children[index],
- ),
- ),
- Breakpoints.mediumAndUp: SlotLayout.from(
- key: const Key('Body Medium'),
- builder: (_) =>
- GridView.count(crossAxisCount: 2, children: children),
- )
- },
+ },
+ ),
+ // Body switches between a ListView and a GridView from small to medium
+ // breakpoints and onwards.
+ body: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig>{
+ Breakpoints.small: SlotLayout.from(
+ key: const Key('Body Small'),
+ builder: (_) => ListView.builder(
+ itemCount: children.length,
+ itemBuilder: (BuildContext context, int index) => children[index],
+ ),
),
- // BottomNavigation is only active in small views defined as under 600 dp
- // width.
- bottomNavigation: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig>{
- Breakpoints.small: SlotLayout.from(
- key: const Key('Bottom Navigation Small'),
- inAnimation: AdaptiveScaffold.bottomToTop,
- outAnimation: AdaptiveScaffold.topToBottom,
- builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
- destinations: destinations,
- currentIndex: selectedNavigation,
- onDestinationSelected: (int newIndex) {
- setState(() {
- selectedNavigation = newIndex;
- });
- },
- ),
- )
- },
- ),
- );
- }
-}
+ Breakpoints.mediumAndUp: SlotLayout.from(
+ key: const Key('Body Medium'),
+ builder: (_) =>
+ GridView.count(crossAxisCount: 2, children: children),
+ )
+ },
+ ),
+ // BottomNavigation is only active in small views defined as under 600 dp
+ // width.
+ bottomNavigation: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig>{
+ Breakpoints.small: SlotLayout.from(
+ key: const Key('Bottom Navigation Small'),
+ inAnimation: AdaptiveScaffold.bottomToTop,
+ outAnimation: AdaptiveScaffold.topToBottom,
+ builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
+ destinations: destinations,
+ currentIndex: selectedNavigation,
+ onDestinationSelected: (int newIndex) {
+ setState(() {
+ selectedNavigation = newIndex;
+ });
+ },
+ ),
+ )
+ },
+ ),
+);
```
Both of the examples shown here produce the same output:
diff --git a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart
index eb95220..77b5cd7 100644
--- a/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart
+++ b/packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart
@@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(goderbauer): Remove this ignore when this package requires Flutter 3.8 or later.
-// ignore_for_file: prefer_const_constructors
-
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
@@ -58,8 +55,8 @@
children: <Widget>[
const Divider(color: Colors.black),
const SizedBox(height: 10),
- Row(
- children: const <Widget>[
+ const Row(
+ children: <Widget>[
SizedBox(width: 27),
Text('Folders', style: TextStyle(fontSize: 16)),
],
@@ -74,8 +71,8 @@
iconSize: 21,
),
const SizedBox(width: 21),
- Flexible(
- child: const Text(
+ const Flexible(
+ child: Text(
'Freelance',
overflow: TextOverflow.ellipsis,
),
@@ -92,8 +89,8 @@
iconSize: 21,
),
const SizedBox(width: 21),
- Flexible(
- child: const Text(
+ const Flexible(
+ child: Text(
'Mortgage',
overflow: TextOverflow.ellipsis,
),
@@ -198,9 +195,9 @@
});
},
extended: true,
- leading: Row(
+ leading: const Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: const <Widget>[
+ children: <Widget>[
Text(
'REPLY',
style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
@@ -260,6 +257,6 @@
},
),
);
- // #enddocregion
+ // #enddocregion Example
}
}
diff --git a/packages/flutter_adaptive_scaffold/example/lib/main.dart b/packages/flutter_adaptive_scaffold/example/lib/main.dart
index 5e97f06..4aac90d 100644
--- a/packages/flutter_adaptive_scaffold/example/lib/main.dart
+++ b/packages/flutter_adaptive_scaffold/example/lib/main.dart
@@ -52,6 +52,9 @@
// the navigation elements.
ValueNotifier<bool?> showGridView = ValueNotifier<bool?>(false);
+ // Override the application's directionality.
+ TextDirection directionalityOverride = TextDirection.ltr;
+
// The index of the selected mail card.
int? selected;
@@ -118,70 +121,96 @@
@override
Widget build(BuildContext context) {
- final Widget trailingNavRail = Column(
- children: <Widget>[
- const Divider(color: Colors.white, thickness: 1.5),
- const SizedBox(height: 10),
- Row(children: <Widget>[
- const SizedBox(width: 22),
- Text('Folders',
- style: TextStyle(fontSize: 13, color: Colors.grey[700]))
- ]),
- const SizedBox(height: 22),
- Row(
- children: <Widget>[
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
+ final Widget trailingNavRail = Expanded(
+ child: Column(
+ children: <Widget>[
+ const Divider(color: Colors.white, thickness: 1.5),
+ const SizedBox(height: 10),
+ Row(children: <Widget>[
+ const SizedBox(width: 22),
+ Text('Folders',
+ style: TextStyle(fontSize: 13, color: Colors.grey[700]))
+ ]),
+ const SizedBox(height: 22),
+ Row(
+ children: <Widget>[
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Text('Freelance', overflow: TextOverflow.ellipsis),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: <Widget>[
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Text('Mortgage', overflow: TextOverflow.ellipsis),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: <Widget>[
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Flexible(
+ child: Text('Taxes', overflow: TextOverflow.ellipsis))
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: <Widget>[
+ const SizedBox(width: 16),
+ IconButton(
+ onPressed: () {},
+ icon: const Icon(Icons.folder_copy_outlined),
+ iconSize: 21,
+ ),
+ const SizedBox(width: 21),
+ const Flexible(
+ child: Text('Receipts', overflow: TextOverflow.ellipsis))
+ ],
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: SwitchListTile.adaptive(
+ title: const Text(
+ 'Directionality',
+ style: TextStyle(fontSize: 12),
+ ),
+ subtitle: Text(
+ directionalityOverride == TextDirection.ltr ? 'LTR' : 'RTL',
+ ),
+ value: directionalityOverride == TextDirection.ltr,
+ onChanged: (bool value) {
+ setState(() {
+ if (value) {
+ directionalityOverride = TextDirection.ltr;
+ } else {
+ directionalityOverride = TextDirection.rtl;
+ }
+ });
+ },
+ ),
),
- const SizedBox(width: 21),
- const Text('Freelance', overflow: TextOverflow.ellipsis),
- ],
- ),
- const SizedBox(height: 16),
- Row(
- children: <Widget>[
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
- ),
- const SizedBox(width: 21),
- const Text('Mortgage', overflow: TextOverflow.ellipsis),
- ],
- ),
- const SizedBox(height: 16),
- Row(
- children: <Widget>[
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
- ),
- const SizedBox(width: 21),
- const Flexible(
- child: Text('Taxes', overflow: TextOverflow.ellipsis))
- ],
- ),
- const SizedBox(height: 16),
- Row(
- children: <Widget>[
- const SizedBox(width: 16),
- IconButton(
- onPressed: () {},
- icon: const Icon(Icons.folder_copy_outlined),
- iconSize: 21,
- ),
- const SizedBox(width: 21),
- const Flexible(
- child: Text('Receipts', overflow: TextOverflow.ellipsis))
- ],
- ),
- ],
+ ),
+ ],
+ ),
);
// These are the destinations used within the AdaptiveScaffold navigation
@@ -208,134 +237,137 @@
// Updating the listener value.
showGridView.value = Breakpoints.mediumAndUp.isActive(context);
- return Scaffold(
- backgroundColor: const Color.fromARGB(255, 234, 227, 241),
- // Usage of AdaptiveLayout suite begins here. AdaptiveLayout takes
- // LayoutSlots for its variety of screen slots.
- body: AdaptiveLayout(
- // Each SlotLayout has a config which maps Breakpoints to
- // SlotLayoutConfigs.
- primaryNavigation: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig?>{
- // The breakpoint used here is from the Breakpoints class but custom
- // Breakpoints can be defined by extending the Breakpoint class
- Breakpoints.medium: SlotLayout.from(
- // Every SlotLayoutConfig takes a key and a builder. The builder
- // is to save memory that would be spent on initialization.
- key: const Key('primaryNavigation'),
- builder: (_) {
- return AdaptiveScaffold.standardNavigationRail(
- // Usually it would be easier to use a builder from
- // AdaptiveScaffold for these types of navigation but this
- // navigation has custom staggered item animations.
+ return Directionality(
+ textDirection: directionalityOverride,
+ child: Scaffold(
+ backgroundColor: const Color.fromARGB(255, 234, 227, 241),
+ // Usage of AdaptiveLayout suite begins here. AdaptiveLayout takes
+ // LayoutSlots for its variety of screen slots.
+ body: AdaptiveLayout(
+ // Each SlotLayout has a config which maps Breakpoints to
+ // SlotLayoutConfigs.
+ primaryNavigation: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig?>{
+ // The breakpoint used here is from the Breakpoints class but custom
+ // Breakpoints can be defined by extending the Breakpoint class
+ Breakpoints.medium: SlotLayout.from(
+ // Every SlotLayoutConfig takes a key and a builder. The builder
+ // is to save memory that would be spent on initialization.
+ key: const Key('primaryNavigation'),
+ builder: (_) {
+ return AdaptiveScaffold.standardNavigationRail(
+ // Usually it would be easier to use a builder from
+ // AdaptiveScaffold for these types of navigation but this
+ // navigation has custom staggered item animations.
+ onDestinationSelected: (int index) {
+ setState(() {
+ _navigationIndex = index;
+ });
+ },
+ selectedIndex: _navigationIndex,
+ leading: ScaleTransition(
+ scale: _articleIconSlideController,
+ child: const _MediumComposeIcon(),
+ ),
+ backgroundColor: const Color.fromARGB(0, 255, 255, 255),
+ destinations: <NavigationRailDestination>[
+ slideInNavigationItem(
+ begin: -1,
+ controller: _inboxIconSlideController,
+ icon: Icons.inbox,
+ label: 'Inbox',
+ ),
+ slideInNavigationItem(
+ begin: -2,
+ controller: _articleIconSlideController,
+ icon: Icons.article_outlined,
+ label: 'Articles',
+ ),
+ slideInNavigationItem(
+ begin: -3,
+ controller: _chatIconSlideController,
+ icon: Icons.chat_bubble_outline,
+ label: 'Chat',
+ ),
+ slideInNavigationItem(
+ begin: -4,
+ controller: _videoIconSlideController,
+ icon: Icons.video_call_outlined,
+ label: 'Video',
+ )
+ ],
+ );
+ },
+ ),
+ Breakpoints.large: SlotLayout.from(
+ key: const Key('Large primaryNavigation'),
+ // The AdaptiveScaffold builder here greatly simplifies
+ // navigational elements.
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ leading: const _LargeComposeIcon(),
onDestinationSelected: (int index) {
setState(() {
_navigationIndex = index;
});
},
selectedIndex: _navigationIndex,
- leading: ScaleTransition(
- scale: _articleIconSlideController,
- child: const _MediumComposeIcon(),
- ),
- backgroundColor: const Color.fromARGB(0, 255, 255, 255),
- destinations: <NavigationRailDestination>[
- slideInNavigationItem(
- begin: -1,
- controller: _inboxIconSlideController,
- icon: Icons.inbox,
- label: 'Inbox',
- ),
- slideInNavigationItem(
- begin: -2,
- controller: _articleIconSlideController,
- icon: Icons.article_outlined,
- label: 'Articles',
- ),
- slideInNavigationItem(
- begin: -3,
- controller: _chatIconSlideController,
- icon: Icons.chat_bubble_outline,
- label: 'Chat',
- ),
- slideInNavigationItem(
- begin: -4,
- controller: _videoIconSlideController,
- icon: Icons.video_call_outlined,
- label: 'Video',
- )
- ],
- );
- },
- ),
- Breakpoints.large: SlotLayout.from(
- key: const Key('Large primaryNavigation'),
- // The AdaptiveScaffold builder here greatly simplifies
- // navigational elements.
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- leading: const _LargeComposeIcon(),
- onDestinationSelected: (int index) {
- setState(() {
- _navigationIndex = index;
- });
- },
- selectedIndex: _navigationIndex,
- trailing: trailingNavRail,
- extended: true,
- destinations: destinations.map((_) {
- return AdaptiveScaffold.toRailDestination(_);
- }).toList(),
+ trailing: trailingNavRail,
+ extended: true,
+ destinations: destinations.map((_) {
+ return AdaptiveScaffold.toRailDestination(_);
+ }).toList(),
+ ),
),
- ),
- },
- ),
- body: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig?>{
- Breakpoints.standard: SlotLayout.from(
- key: const Key('body'),
- // The conditional here is for navigation screens. The first
- // screen shows the main screen and every other screen shows
- // ExamplePage.
- builder: (_) => (_navigationIndex == 0)
- ? Padding(
- padding: const EdgeInsets.fromLTRB(0, 32, 0, 0),
- child: _ItemList(
- selected: selected,
- items: _allItems,
- selectCard: selectCard,
+ },
+ ),
+ body: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig?>{
+ Breakpoints.standard: SlotLayout.from(
+ key: const Key('body'),
+ // The conditional here is for navigation screens. The first
+ // screen shows the main screen and every other screen shows
+ // ExamplePage.
+ builder: (_) => (_navigationIndex == 0)
+ ? Padding(
+ padding: const EdgeInsets.fromLTRB(0, 32, 0, 0),
+ child: _ItemList(
+ selected: selected,
+ items: _allItems,
+ selectCard: selectCard,
+ ),
+ )
+ : const _ExamplePage(),
+ ),
+ },
+ ),
+ secondaryBody: _navigationIndex == 0
+ ? SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig?>{
+ Breakpoints.mediumAndUp: SlotLayout.from(
+ // This overrides the default behavior of the secondaryBody
+ // disappearing as it is animating out.
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ key: const Key('Secondary Body'),
+ builder: (_) => SafeArea(
+ child: _DetailTile(item: _allItems[selected ?? 0]),
),
)
- : const _ExamplePage(),
- ),
- },
- ),
- secondaryBody: _navigationIndex == 0
- ? SlotLayout(
- config: <Breakpoint, SlotLayoutConfig?>{
- Breakpoints.mediumAndUp: SlotLayout.from(
- // This overrides the default behavior of the secondaryBody
- // disappearing as it is animating out.
- outAnimation: AdaptiveScaffold.stayOnScreen,
- key: const Key('Secondary Body'),
- builder: (_) => SafeArea(
- child: _DetailTile(item: _allItems[selected ?? 0]),
- ),
- )
- },
+ },
+ )
+ : null,
+ bottomNavigation: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig?>{
+ Breakpoints.small: SlotLayout.from(
+ key: const Key('bottomNavigation'),
+ // You can define inAnimations or outAnimations to override the
+ // default offset transition.
+ outAnimation: AdaptiveScaffold.topToBottom,
+ builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
+ destinations: destinations,
+ ),
)
- : null,
- bottomNavigation: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig?>{
- Breakpoints.small: SlotLayout.from(
- key: const Key('bottomNavigation'),
- // You can define inAnimations or outAnimations to override the
- // default offset transition.
- outAnimation: AdaptiveScaffold.topToBottom,
- builder: (_) => AdaptiveScaffold.standardBottomNavigationBar(
- destinations: destinations,
- ),
- )
- },
+ },
+ ),
),
),
);
diff --git a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart
index 028f47e..f3485ae 100644
--- a/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart
+++ b/packages/flutter_adaptive_scaffold/lib/src/adaptive_scaffold.dart
@@ -488,165 +488,160 @@
final NavigationRailThemeData navRailTheme =
Theme.of(context).navigationRailTheme;
- return Directionality(
- textDirection: TextDirection.ltr,
- child: Scaffold(
- appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
- ? widget.appBar ?? AppBar()
- : null,
- drawer: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
- ? Drawer(
- child: NavigationRail(
- extended: true,
- leading: widget.leadingExtendedNavRail,
- trailing: widget.trailingNavRail,
- selectedIndex: widget.selectedIndex,
- destinations: widget.destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- onDestinationSelected: widget.onSelectedIndexChange,
- ),
- )
- : null,
- body: AdaptiveLayout(
- bodyOrientation: widget.bodyOrientation,
- bodyRatio: widget.bodyRatio,
- internalAnimations: widget.internalAnimations,
- primaryNavigation: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig>{
- widget.mediumBreakpoint: SlotLayout.from(
- key: const Key('primaryNavigation'),
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- width: widget.navigationRailWidth,
- leading: widget.leadingUnextendedNavRail,
- trailing: widget.trailingNavRail,
- selectedIndex: widget.selectedIndex,
- destinations: widget.destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- onDestinationSelected: widget.onSelectedIndexChange,
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle:
- navRailTheme.unselectedLabelTextStyle,
- ),
+ return Scaffold(
+ appBar: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
+ ? widget.appBar ?? AppBar()
+ : null,
+ drawer: widget.drawerBreakpoint.isActive(context) && widget.useDrawer
+ ? Drawer(
+ child: NavigationRail(
+ extended: true,
+ leading: widget.leadingExtendedNavRail,
+ trailing: widget.trailingNavRail,
+ selectedIndex: widget.selectedIndex,
+ destinations: widget.destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ onDestinationSelected: widget.onSelectedIndexChange,
),
- widget.largeBreakpoint: SlotLayout.from(
- key: const Key('primaryNavigation1'),
- builder: (_) => AdaptiveScaffold.standardNavigationRail(
- width: widget.extendedNavigationRailWidth,
- extended: true,
- leading: widget.leadingExtendedNavRail,
- trailing: widget.trailingNavRail,
- selectedIndex: widget.selectedIndex,
- destinations: widget.destinations
- .map((_) => AdaptiveScaffold.toRailDestination(_))
- .toList(),
- onDestinationSelected: widget.onSelectedIndexChange,
- backgroundColor: navRailTheme.backgroundColor,
- selectedIconTheme: navRailTheme.selectedIconTheme,
- unselectedIconTheme: navRailTheme.unselectedIconTheme,
- selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
- unSelectedLabelTextStyle:
- navRailTheme.unselectedLabelTextStyle,
- ),
+ )
+ : null,
+ body: AdaptiveLayout(
+ bodyOrientation: widget.bodyOrientation,
+ bodyRatio: widget.bodyRatio,
+ internalAnimations: widget.internalAnimations,
+ primaryNavigation: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig>{
+ widget.mediumBreakpoint: SlotLayout.from(
+ key: const Key('primaryNavigation'),
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ width: widget.navigationRailWidth,
+ leading: widget.leadingUnextendedNavRail,
+ trailing: widget.trailingNavRail,
+ selectedIndex: widget.selectedIndex,
+ destinations: widget.destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ onDestinationSelected: widget.onSelectedIndexChange,
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
),
- },
- ),
- bottomNavigation:
- !widget.drawerBreakpoint.isActive(context) || !widget.useDrawer
- ? SlotLayout(
- config: <Breakpoint, SlotLayoutConfig>{
- widget.smallBreakpoint: SlotLayout.from(
- key: const Key('bottomNavigation'),
- builder: (_) =>
- AdaptiveScaffold.standardBottomNavigationBar(
- currentIndex: widget.selectedIndex,
- destinations: widget.destinations,
- onDestinationSelected: widget.onSelectedIndexChange,
- ),
+ ),
+ widget.largeBreakpoint: SlotLayout.from(
+ key: const Key('primaryNavigation1'),
+ builder: (_) => AdaptiveScaffold.standardNavigationRail(
+ width: widget.extendedNavigationRailWidth,
+ extended: true,
+ leading: widget.leadingExtendedNavRail,
+ trailing: widget.trailingNavRail,
+ selectedIndex: widget.selectedIndex,
+ destinations: widget.destinations
+ .map((_) => AdaptiveScaffold.toRailDestination(_))
+ .toList(),
+ onDestinationSelected: widget.onSelectedIndexChange,
+ backgroundColor: navRailTheme.backgroundColor,
+ selectedIconTheme: navRailTheme.selectedIconTheme,
+ unselectedIconTheme: navRailTheme.unselectedIconTheme,
+ selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
+ unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
+ ),
+ ),
+ },
+ ),
+ bottomNavigation:
+ !widget.drawerBreakpoint.isActive(context) || !widget.useDrawer
+ ? SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig>{
+ widget.smallBreakpoint: SlotLayout.from(
+ key: const Key('bottomNavigation'),
+ builder: (_) =>
+ AdaptiveScaffold.standardBottomNavigationBar(
+ currentIndex: widget.selectedIndex,
+ destinations: widget.destinations,
+ onDestinationSelected: widget.onSelectedIndexChange,
),
- },
- )
- : null,
- body: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig?>{
- Breakpoints.standard: SlotLayout.from(
- key: const Key('body'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.body,
- ),
- if (widget.smallBody != null)
- widget.smallBreakpoint:
- (widget.smallBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('smallBody'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.smallBody,
- )
- : null,
- if (widget.body != null)
- widget.mediumBreakpoint:
- (widget.body != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('body'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.body,
- )
- : null,
- if (widget.largeBody != null)
- widget.largeBreakpoint:
- (widget.largeBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('largeBody'),
- inAnimation: AdaptiveScaffold.fadeIn,
- outAnimation: AdaptiveScaffold.fadeOut,
- builder: widget.largeBody,
- )
- : null,
- },
- ),
- secondaryBody: SlotLayout(
- config: <Breakpoint, SlotLayoutConfig?>{
- Breakpoints.standard: SlotLayout.from(
- key: const Key('sBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.secondaryBody,
- ),
- if (widget.smallSecondaryBody != null)
- widget.smallBreakpoint:
- (widget.smallSecondaryBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('smallSBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.smallSecondaryBody,
- )
- : null,
- if (widget.secondaryBody != null)
- widget.mediumBreakpoint:
- (widget.secondaryBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('sBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.secondaryBody,
- )
- : null,
- if (widget.largeSecondaryBody != null)
- widget.largeBreakpoint:
- (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder)
- ? SlotLayout.from(
- key: const Key('largeSBody'),
- outAnimation: AdaptiveScaffold.stayOnScreen,
- builder: widget.largeSecondaryBody,
- )
- : null,
- },
- ),
+ ),
+ },
+ )
+ : null,
+ body: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig?>{
+ Breakpoints.standard: SlotLayout.from(
+ key: const Key('body'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.body,
+ ),
+ if (widget.smallBody != null)
+ widget.smallBreakpoint:
+ (widget.smallBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('smallBody'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.smallBody,
+ )
+ : null,
+ if (widget.body != null)
+ widget.mediumBreakpoint:
+ (widget.body != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('body'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.body,
+ )
+ : null,
+ if (widget.largeBody != null)
+ widget.largeBreakpoint:
+ (widget.largeBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('largeBody'),
+ inAnimation: AdaptiveScaffold.fadeIn,
+ outAnimation: AdaptiveScaffold.fadeOut,
+ builder: widget.largeBody,
+ )
+ : null,
+ },
+ ),
+ secondaryBody: SlotLayout(
+ config: <Breakpoint, SlotLayoutConfig?>{
+ Breakpoints.standard: SlotLayout.from(
+ key: const Key('sBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.secondaryBody,
+ ),
+ if (widget.smallSecondaryBody != null)
+ widget.smallBreakpoint:
+ (widget.smallSecondaryBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('smallSBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.smallSecondaryBody,
+ )
+ : null,
+ if (widget.secondaryBody != null)
+ widget.mediumBreakpoint:
+ (widget.secondaryBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('sBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.secondaryBody,
+ )
+ : null,
+ if (widget.largeSecondaryBody != null)
+ widget.largeBreakpoint:
+ (widget.largeSecondaryBody != AdaptiveScaffold.emptyBuilder)
+ ? SlotLayout.from(
+ key: const Key('largeSBody'),
+ outAnimation: AdaptiveScaffold.stayOnScreen,
+ builder: widget.largeSecondaryBody,
+ )
+ : null,
+ },
),
),
);
diff --git a/packages/flutter_adaptive_scaffold/pubspec.yaml b/packages/flutter_adaptive_scaffold/pubspec.yaml
index 4e3cc5c..155e086 100644
--- a/packages/flutter_adaptive_scaffold/pubspec.yaml
+++ b/packages/flutter_adaptive_scaffold/pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_adaptive_scaffold
description: Widgets to easily build adaptive layouts, including navigation elements.
-version: 0.1.4
+version: 0.1.5
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_adaptive_scaffold%22
repository: https://github.com/flutter/packages/tree/main/packages/flutter_adaptive_scaffold
diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
index 90fb25b..5a84f44 100644
--- a/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
+++ b/packages/flutter_adaptive_scaffold/test/adaptive_layout_test.dart
@@ -170,9 +170,7 @@
expect(begin, findsOneWidget);
expect(end, findsOneWidget);
}
- // TODO(gspencergoog): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
testWidgets('slot layout can tolerate rapid changes in breakpoints',
(WidgetTester tester) async {
@@ -191,9 +189,7 @@
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
- // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
// This test reflects the behavior of the internal animations of both the body
// and secondary body and also the navigational items. This is reflected in
@@ -248,9 +244,7 @@
expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10));
expect(
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
- // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
testWidgets('adaptive layout does not animate when animations off',
(WidgetTester tester) async {
@@ -269,9 +263,7 @@
expect(tester.getTopLeft(secondaryTestBreakpoint), const Offset(200, 10));
expect(
tester.getBottomRight(secondaryTestBreakpoint), const Offset(390, 790));
- // TODO(a-wallen): Remove skip when AnimatedSwitcher fix rolls into stable.
- // https://github.com/flutter/flutter/pull/107476
- }, skip: true);
+ });
}
class TestBreakpoint0 extends Breakpoint {
diff --git a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart
index 3256286..463dd84 100644
--- a/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart
+++ b/packages/flutter_adaptive_scaffold/test/adaptive_scaffold_test.dart
@@ -543,6 +543,43 @@
tester.widget<NavigationRail>(find.byType(NavigationRail));
expect(rail.groupAlignment, equals(groupAlignment));
});
+
+ testWidgets(
+ "doesn't override Directionality",
+ (WidgetTester tester) async {
+ const List<NavigationDestination> destinations = <NavigationDestination>[
+ NavigationDestination(
+ icon: Icon(Icons.home),
+ label: 'Home',
+ ),
+ NavigationDestination(
+ icon: Icon(Icons.account_circle),
+ label: 'Profile',
+ ),
+ ];
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: Directionality(
+ textDirection: TextDirection.rtl,
+ child: AdaptiveScaffold(
+ destinations: destinations,
+ body: (BuildContext context) {
+ return const SizedBox.shrink();
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final Finder body = find.byKey(const Key('body'));
+ expect(body, findsOneWidget);
+ final TextDirection dir = Directionality.of(body.evaluate().first);
+ expect(dir, TextDirection.rtl);
+ },
+ );
}
/// An empty widget that implements [PreferredSizeWidget] to ensure that