home.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. // Copyright 2021 The Flutter team. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. import 'package:flutter/material.dart';
  5. import 'color_palettes_screen.dart';
  6. import 'component_screen.dart';
  7. import 'constants.dart';
  8. import 'elevation_screen.dart';
  9. import 'typography_screen.dart';
  10. class Home extends StatefulWidget {
  11. const Home({
  12. super.key,
  13. required this.useLightMode,
  14. required this.useMaterial3,
  15. required this.colorSelected,
  16. required this.handleBrightnessChange,
  17. required this.handleMaterialVersionChange,
  18. required this.handleColorSelect,
  19. });
  20. final bool useLightMode;
  21. final bool useMaterial3;
  22. final ColorSeed colorSelected;
  23. final void Function(bool useLightMode) handleBrightnessChange;
  24. final void Function() handleMaterialVersionChange;
  25. final void Function(int value) handleColorSelect;
  26. @override
  27. State<Home> createState() => _HomeState();
  28. }
  29. class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
  30. final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
  31. late final AnimationController controller;
  32. late final CurvedAnimation railAnimation;
  33. bool controllerInitialized = false;
  34. bool showMediumSizeLayout = false;
  35. bool showLargeSizeLayout = false;
  36. int screenIndex = ScreenSelected.component.value;
  37. @override
  38. initState() {
  39. super.initState();
  40. controller = AnimationController(
  41. duration: Duration(milliseconds: transitionLength.toInt() * 2),
  42. value: 0,
  43. vsync: this,
  44. );
  45. railAnimation = CurvedAnimation(
  46. parent: controller,
  47. curve: const Interval(0.5, 1.0),
  48. );
  49. }
  50. @override
  51. void dispose() {
  52. controller.dispose();
  53. super.dispose();
  54. }
  55. @override
  56. void didChangeDependencies() {
  57. super.didChangeDependencies();
  58. final double width = MediaQuery.of(context).size.width;
  59. final AnimationStatus status = controller.status;
  60. if (width > mediumWidthBreakpoint) {
  61. if (width > largeWidthBreakpoint) {
  62. showMediumSizeLayout = false;
  63. showLargeSizeLayout = true;
  64. } else {
  65. showMediumSizeLayout = true;
  66. showLargeSizeLayout = false;
  67. }
  68. if (status != AnimationStatus.forward &&
  69. status != AnimationStatus.completed) {
  70. controller.forward();
  71. }
  72. } else {
  73. showMediumSizeLayout = false;
  74. showLargeSizeLayout = false;
  75. if (status != AnimationStatus.reverse &&
  76. status != AnimationStatus.dismissed) {
  77. controller.reverse();
  78. }
  79. }
  80. if (!controllerInitialized) {
  81. controllerInitialized = true;
  82. controller.value = width > mediumWidthBreakpoint ? 1 : 0;
  83. }
  84. }
  85. void handleScreenChanged(int screenSelected) {
  86. setState(() {
  87. screenIndex = screenSelected;
  88. });
  89. }
  90. Widget createScreenFor(
  91. ScreenSelected screenSelected, bool showNavBarExample) {
  92. switch (screenSelected) {
  93. case ScreenSelected.component:
  94. return Expanded(
  95. child: OneTwoTransition(
  96. animation: railAnimation,
  97. one: FirstComponentList(
  98. showNavBottomBar: showNavBarExample,
  99. scaffoldKey: scaffoldKey,
  100. showSecondList: showMediumSizeLayout || showLargeSizeLayout),
  101. two: SecondComponentList(
  102. scaffoldKey: scaffoldKey,
  103. ),
  104. ),
  105. );
  106. case ScreenSelected.color:
  107. return const ColorPalettesScreen();
  108. case ScreenSelected.typography:
  109. return const TypographyScreen();
  110. case ScreenSelected.elevation:
  111. return const ElevationScreen();
  112. default:
  113. return FirstComponentList(
  114. showNavBottomBar: showNavBarExample,
  115. scaffoldKey: scaffoldKey,
  116. showSecondList: showMediumSizeLayout || showLargeSizeLayout);
  117. }
  118. }
  119. PreferredSizeWidget createAppBar() {
  120. return AppBar(
  121. title: widget.useMaterial3
  122. ? const Text('Material 3')
  123. : const Text('Material 2'),
  124. actions: !showMediumSizeLayout && !showLargeSizeLayout
  125. ? [
  126. _BrightnessButton(
  127. handleBrightnessChange: widget.handleBrightnessChange,
  128. ),
  129. _Material3Button(
  130. handleMaterialVersionChange: widget.handleMaterialVersionChange,
  131. ),
  132. _ColorSeedButton(
  133. handleColorSelect: widget.handleColorSelect,
  134. colorSelected: widget.colorSelected,
  135. ),
  136. ]
  137. : [Container()],
  138. );
  139. }
  140. Widget _expandedTrailingActions() => Container(
  141. constraints: const BoxConstraints.tightFor(width: 250),
  142. padding: const EdgeInsets.symmetric(horizontal: 30),
  143. child: Column(
  144. mainAxisAlignment: MainAxisAlignment.end,
  145. crossAxisAlignment: CrossAxisAlignment.stretch,
  146. children: [
  147. Row(
  148. children: [
  149. const Text('Brightness'),
  150. Expanded(child: Container()),
  151. Switch(
  152. value: widget.useLightMode,
  153. onChanged: (value) {
  154. widget.handleBrightnessChange(value);
  155. })
  156. ],
  157. ),
  158. Row(
  159. children: [
  160. widget.useMaterial3
  161. ? const Text('Material 3')
  162. : const Text('Material 2'),
  163. Expanded(child: Container()),
  164. Switch(
  165. value: widget.useMaterial3,
  166. onChanged: (_) {
  167. widget.handleMaterialVersionChange();
  168. })
  169. ],
  170. ),
  171. const Divider(),
  172. ConstrainedBox(
  173. constraints: const BoxConstraints(maxHeight: 200.0),
  174. child: GridView.count(
  175. crossAxisCount: 3,
  176. children: List.generate(
  177. ColorSeed.values.length,
  178. (i) => IconButton(
  179. icon: const Icon(Icons.radio_button_unchecked),
  180. color: ColorSeed.values[i].color,
  181. isSelected: widget.colorSelected.color ==
  182. ColorSeed.values[i].color,
  183. selectedIcon: const Icon(Icons.circle),
  184. onPressed: () {
  185. widget.handleColorSelect(i);
  186. },
  187. )),
  188. ),
  189. ),
  190. ],
  191. ),
  192. );
  193. Widget _trailingActions() => Column(
  194. mainAxisAlignment: MainAxisAlignment.end,
  195. children: [
  196. Flexible(
  197. child: _BrightnessButton(
  198. handleBrightnessChange: widget.handleBrightnessChange,
  199. showTooltipBelow: false,
  200. ),
  201. ),
  202. Flexible(
  203. child: _Material3Button(
  204. handleMaterialVersionChange: widget.handleMaterialVersionChange,
  205. showTooltipBelow: false,
  206. ),
  207. ),
  208. Flexible(
  209. child: _ColorSeedButton(
  210. handleColorSelect: widget.handleColorSelect,
  211. colorSelected: widget.colorSelected,
  212. ),
  213. ),
  214. ],
  215. );
  216. @override
  217. Widget build(BuildContext context) {
  218. return AnimatedBuilder(
  219. animation: controller,
  220. builder: (context, child) {
  221. return NavigationTransition(
  222. scaffoldKey: scaffoldKey,
  223. animationController: controller,
  224. railAnimation: railAnimation,
  225. appBar: createAppBar(),
  226. body: createScreenFor(
  227. ScreenSelected.values[screenIndex], controller.value == 1),
  228. navigationRail: NavigationRail(
  229. extended: showLargeSizeLayout,
  230. destinations: navRailDestinations,
  231. selectedIndex: screenIndex,
  232. onDestinationSelected: (index) {
  233. setState(() {
  234. screenIndex = index;
  235. handleScreenChanged(screenIndex);
  236. });
  237. },
  238. trailing: Expanded(
  239. child: Padding(
  240. padding: const EdgeInsets.only(bottom: 20),
  241. child: showLargeSizeLayout
  242. ? _expandedTrailingActions()
  243. : _trailingActions(),
  244. ),
  245. ),
  246. ),
  247. navigationBar: NavigationBars(
  248. onSelectItem: (index) {
  249. setState(() {
  250. screenIndex = index;
  251. handleScreenChanged(screenIndex);
  252. });
  253. },
  254. selectedIndex: screenIndex,
  255. isExampleBar: false,
  256. ),
  257. );
  258. },
  259. );
  260. }
  261. }
  262. class _BrightnessButton extends StatelessWidget {
  263. const _BrightnessButton({
  264. required this.handleBrightnessChange,
  265. this.showTooltipBelow = true,
  266. });
  267. final Function handleBrightnessChange;
  268. final bool showTooltipBelow;
  269. @override
  270. Widget build(BuildContext context) {
  271. final isBright = Theme.of(context).brightness == Brightness.light;
  272. return Tooltip(
  273. preferBelow: showTooltipBelow,
  274. message: 'Toggle brightness',
  275. child: IconButton(
  276. icon: isBright
  277. ? const Icon(Icons.dark_mode_outlined)
  278. : const Icon(Icons.light_mode_outlined),
  279. onPressed: () => handleBrightnessChange(!isBright),
  280. ),
  281. );
  282. }
  283. }
  284. class _Material3Button extends StatelessWidget {
  285. const _Material3Button({
  286. required this.handleMaterialVersionChange,
  287. this.showTooltipBelow = true,
  288. });
  289. final void Function() handleMaterialVersionChange;
  290. final bool showTooltipBelow;
  291. @override
  292. Widget build(BuildContext context) {
  293. final useMaterial3 = Theme.of(context).useMaterial3;
  294. return Tooltip(
  295. preferBelow: showTooltipBelow,
  296. message: 'Switch to Material ${useMaterial3 ? 2 : 3}',
  297. child: IconButton(
  298. icon: useMaterial3
  299. ? const Icon(Icons.filter_2)
  300. : const Icon(Icons.filter_3),
  301. onPressed: handleMaterialVersionChange,
  302. ),
  303. );
  304. }
  305. }
  306. class _ColorSeedButton extends StatelessWidget {
  307. const _ColorSeedButton({
  308. required this.handleColorSelect,
  309. required this.colorSelected,
  310. });
  311. final void Function(int) handleColorSelect;
  312. final ColorSeed colorSelected;
  313. @override
  314. Widget build(BuildContext context) {
  315. return PopupMenuButton(
  316. icon: Icon(
  317. Icons.palette_outlined,
  318. color: Theme.of(context).colorScheme.onSurfaceVariant,
  319. ),
  320. tooltip: 'Select a seed color',
  321. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
  322. itemBuilder: (context) {
  323. return List.generate(ColorSeed.values.length, (index) {
  324. ColorSeed currentColor = ColorSeed.values[index];
  325. return PopupMenuItem(
  326. value: index,
  327. enabled: currentColor != colorSelected,
  328. child: Wrap(
  329. children: [
  330. Padding(
  331. padding: const EdgeInsets.only(left: 10),
  332. child: Icon(
  333. currentColor == colorSelected
  334. ? Icons.color_lens
  335. : Icons.color_lens_outlined,
  336. color: currentColor.color,
  337. ),
  338. ),
  339. Padding(
  340. padding: const EdgeInsets.only(left: 20),
  341. child: Text(currentColor.label),
  342. ),
  343. ],
  344. ),
  345. );
  346. });
  347. },
  348. onSelected: handleColorSelect,
  349. );
  350. }
  351. }
  352. class NavigationTransition extends StatefulWidget {
  353. const NavigationTransition(
  354. {super.key,
  355. required this.scaffoldKey,
  356. required this.animationController,
  357. required this.railAnimation,
  358. required this.navigationRail,
  359. required this.navigationBar,
  360. required this.appBar,
  361. required this.body});
  362. final GlobalKey<ScaffoldState> scaffoldKey;
  363. final AnimationController animationController;
  364. final CurvedAnimation railAnimation;
  365. final Widget navigationRail;
  366. final Widget navigationBar;
  367. final PreferredSizeWidget appBar;
  368. final Widget body;
  369. @override
  370. State<NavigationTransition> createState() => _NavigationTransitionState();
  371. }
  372. class _NavigationTransitionState extends State<NavigationTransition> {
  373. late final AnimationController controller;
  374. late final CurvedAnimation railAnimation;
  375. late final ReverseAnimation barAnimation;
  376. bool controllerInitialized = false;
  377. bool showDivider = false;
  378. @override
  379. void initState() {
  380. super.initState();
  381. controller = widget.animationController;
  382. railAnimation = widget.railAnimation;
  383. barAnimation = ReverseAnimation(
  384. CurvedAnimation(
  385. parent: controller,
  386. curve: const Interval(0.0, 0.5),
  387. ),
  388. );
  389. }
  390. @override
  391. Widget build(BuildContext context) {
  392. final ColorScheme colorScheme = Theme.of(context).colorScheme;
  393. return Scaffold(
  394. key: widget.scaffoldKey,
  395. appBar: widget.appBar,
  396. body: Row(
  397. children: <Widget>[
  398. RailTransition(
  399. animation: railAnimation,
  400. backgroundColor: colorScheme.surface,
  401. child: widget.navigationRail,
  402. ),
  403. widget.body,
  404. ],
  405. ),
  406. bottomNavigationBar: BarTransition(
  407. animation: barAnimation,
  408. backgroundColor: colorScheme.surface,
  409. child: widget.navigationBar,
  410. ),
  411. endDrawer: const NavigationDrawerSection(),
  412. );
  413. }
  414. }
  415. final List<NavigationRailDestination> navRailDestinations = appBarDestinations
  416. .map(
  417. (destination) => NavigationRailDestination(
  418. icon: Tooltip(
  419. message: destination.label,
  420. child: destination.icon,
  421. ),
  422. selectedIcon: Tooltip(
  423. message: destination.label,
  424. child: destination.selectedIcon,
  425. ),
  426. label: Text(destination.label),
  427. ),
  428. )
  429. .toList();
  430. class SizeAnimation extends CurvedAnimation {
  431. SizeAnimation(Animation<double> parent)
  432. : super(
  433. parent: parent,
  434. curve: const Interval(
  435. 0.2,
  436. 0.8,
  437. curve: Curves.easeInOutCubicEmphasized,
  438. ),
  439. reverseCurve: Interval(
  440. 0,
  441. 0.2,
  442. curve: Curves.easeInOutCubicEmphasized.flipped,
  443. ),
  444. );
  445. }
  446. class OffsetAnimation extends CurvedAnimation {
  447. OffsetAnimation(Animation<double> parent)
  448. : super(
  449. parent: parent,
  450. curve: const Interval(
  451. 0.4,
  452. 1.0,
  453. curve: Curves.easeInOutCubicEmphasized,
  454. ),
  455. reverseCurve: Interval(
  456. 0,
  457. 0.2,
  458. curve: Curves.easeInOutCubicEmphasized.flipped,
  459. ),
  460. );
  461. }
  462. class RailTransition extends StatefulWidget {
  463. const RailTransition(
  464. {super.key,
  465. required this.animation,
  466. required this.backgroundColor,
  467. required this.child});
  468. final Animation<double> animation;
  469. final Widget child;
  470. final Color backgroundColor;
  471. @override
  472. State<RailTransition> createState() => _RailTransition();
  473. }
  474. class _RailTransition extends State<RailTransition> {
  475. late Animation<Offset> offsetAnimation;
  476. late Animation<double> widthAnimation;
  477. @override
  478. void didChangeDependencies() {
  479. super.didChangeDependencies();
  480. // The animations are only rebuilt by this method when the text
  481. // direction changes because this widget only depends on Directionality.
  482. final bool ltr = Directionality.of(context) == TextDirection.ltr;
  483. widthAnimation = Tween<double>(
  484. begin: 0,
  485. end: 1,
  486. ).animate(SizeAnimation(widget.animation));
  487. offsetAnimation = Tween<Offset>(
  488. begin: ltr ? const Offset(-1, 0) : const Offset(1, 0),
  489. end: Offset.zero,
  490. ).animate(OffsetAnimation(widget.animation));
  491. }
  492. @override
  493. Widget build(BuildContext context) {
  494. return ClipRect(
  495. child: DecoratedBox(
  496. decoration: BoxDecoration(color: widget.backgroundColor),
  497. child: Align(
  498. alignment: Alignment.topLeft,
  499. widthFactor: widthAnimation.value,
  500. child: FractionalTranslation(
  501. translation: offsetAnimation.value,
  502. child: widget.child,
  503. ),
  504. ),
  505. ),
  506. );
  507. }
  508. }
  509. class BarTransition extends StatefulWidget {
  510. const BarTransition(
  511. {super.key,
  512. required this.animation,
  513. required this.backgroundColor,
  514. required this.child});
  515. final Animation<double> animation;
  516. final Color backgroundColor;
  517. final Widget child;
  518. @override
  519. State<BarTransition> createState() => _BarTransition();
  520. }
  521. class _BarTransition extends State<BarTransition> {
  522. late final Animation<Offset> offsetAnimation;
  523. late final Animation<double> heightAnimation;
  524. @override
  525. void initState() {
  526. super.initState();
  527. offsetAnimation = Tween<Offset>(
  528. begin: const Offset(0, 1),
  529. end: Offset.zero,
  530. ).animate(OffsetAnimation(widget.animation));
  531. heightAnimation = Tween<double>(
  532. begin: 0,
  533. end: 1,
  534. ).animate(SizeAnimation(widget.animation));
  535. }
  536. @override
  537. Widget build(BuildContext context) {
  538. return ClipRect(
  539. child: DecoratedBox(
  540. decoration: BoxDecoration(color: widget.backgroundColor),
  541. child: Align(
  542. alignment: Alignment.topLeft,
  543. heightFactor: heightAnimation.value,
  544. child: FractionalTranslation(
  545. translation: offsetAnimation.value,
  546. child: widget.child,
  547. ),
  548. ),
  549. ),
  550. );
  551. }
  552. }
  553. class OneTwoTransition extends StatefulWidget {
  554. const OneTwoTransition({
  555. super.key,
  556. required this.animation,
  557. required this.one,
  558. required this.two,
  559. });
  560. final Animation<double> animation;
  561. final Widget one;
  562. final Widget two;
  563. @override
  564. State<OneTwoTransition> createState() => _OneTwoTransitionState();
  565. }
  566. class _OneTwoTransitionState extends State<OneTwoTransition> {
  567. late final Animation<Offset> offsetAnimation;
  568. late final Animation<double> widthAnimation;
  569. @override
  570. void initState() {
  571. super.initState();
  572. offsetAnimation = Tween<Offset>(
  573. begin: const Offset(1, 0),
  574. end: Offset.zero,
  575. ).animate(OffsetAnimation(widget.animation));
  576. widthAnimation = Tween<double>(
  577. begin: 0,
  578. end: mediumWidthBreakpoint,
  579. ).animate(SizeAnimation(widget.animation));
  580. }
  581. @override
  582. Widget build(BuildContext context) {
  583. return Row(
  584. children: <Widget>[
  585. Flexible(
  586. flex: mediumWidthBreakpoint.toInt(),
  587. child: widget.one,
  588. ),
  589. if (widthAnimation.value.toInt() > 0) ...[
  590. Flexible(
  591. flex: widthAnimation.value.toInt(),
  592. child: FractionalTranslation(
  593. translation: offsetAnimation.value,
  594. child: widget.two,
  595. ),
  596. )
  597. ],
  598. ],
  599. );
  600. }
  601. }