You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/experimental/linting_tool/lib/widgets/adaptive_nav.dart

354 lines
11 KiB

// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show SystemUiOverlayStyle;
import 'package:linting_tool/layout/adaptive.dart';
import 'package:linting_tool/pages/default_lints_page.dart';
import 'package:linting_tool/pages/home_page.dart';
import 'package:linting_tool/pages/saved_lints_page.dart';
import 'package:linting_tool/theme/colors.dart';
final navKey = GlobalKey<NavigatorState>();
class AdaptiveNav extends StatefulWidget {
const AdaptiveNav({super.key});
@override
State<AdaptiveNav> createState() => _AdaptiveNavState();
}
class _AdaptiveNavState extends State<AdaptiveNav> {
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayLarge(context);
const navigationDestinations = <_Destination>[
_Destination(
textLabel: 'Home',
icon: Icons.home_outlined,
selectedIcon: Icons.home,
destination: HomePage(),
),
_Destination(
textLabel: 'Saved Profiles',
icon: Icons.save_outlined,
selectedIcon: Icons.save,
destination: SavedLintsPage(),
),
_Destination(
textLabel: 'Default Profiles',
icon: Icons.featured_play_list_outlined,
selectedIcon: Icons.featured_play_list,
destination: DefaultLintsPage(),
),
];
const trailing = <String, IconData>{
'About': Icons.info_outline,
};
return _NavView(
extended: isDesktop,
destinations: navigationDestinations,
trailing: trailing,
);
}
}
class _NavView extends StatefulWidget {
const _NavView({
required this.extended,
required this.destinations,
this.trailing = const {},
});
final bool extended;
final List<_Destination> destinations;
final Map<String, IconData> trailing;
@override
_NavViewState createState() => _NavViewState();
}
class _NavViewState extends State<_NavView> {
late final ValueNotifier<bool> _isExtended;
var _selectedIndex = 0;
@override
void initState() {
super.initState();
_isExtended = ValueNotifier<bool>(widget.extended);
}
void _onDestinationSelected(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(
title: Text(
'Flutter Linting Tool',
style: textTheme.subtitle2!.copyWith(
color: textTheme.bodyText1!.color,
),
),
toolbarHeight: 38.0,
backgroundColor: Colors.white,
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
body: Row(
children: [
LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
clipBehavior: Clip.antiAlias,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: ValueListenableBuilder<bool>(
valueListenable: _isExtended,
builder: (context, value, child) {
var isSmallDisplay = isDisplaySmall(context);
return NavigationRail(
destinations: [
for (var destination in widget.destinations)
NavigationRailDestination(
icon: Icon(destination.icon),
selectedIcon: destination.selectedIcon != null
? Icon(destination.selectedIcon)
: null,
label: Text(destination.textLabel),
),
],
extended: _isExtended.value && !isSmallDisplay,
labelType: NavigationRailLabelType.none,
leading: _NavigationRailHeader(
extended: _isExtended,
),
trailing: _NavigationRailTrailingSection(
trailingDestinations: widget.trailing,
),
selectedIndex: _selectedIndex,
onDestinationSelected: _onDestinationSelected,
);
},
),
),
),
);
},
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1340),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
switchOutCurve: Curves.easeOut,
switchInCurve: Curves.easeIn,
child: widget.destinations[_selectedIndex].destination,
),
),
),
),
],
),
);
}
}
class _NavigationRailHeader extends StatelessWidget {
const _NavigationRailHeader({
required this.extended,
});
final ValueNotifier<bool?> extended;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final animation = NavigationRail.extendedAnimation(context);
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Align(
alignment: AlignmentDirectional.centerStart,
widthFactor: animation.value,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 56,
child: Row(
children: [
const SizedBox(width: 6),
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16)),
onTap: () {
extended.value = !extended.value!;
},
child: Row(
children: [
Transform.rotate(
angle: animation.value * math.pi,
child: const Icon(
Icons.arrow_left,
color: AppColors.white50,
size: 16,
),
),
const FlutterLogo(),
const SizedBox(width: 10),
Align(
alignment: AlignmentDirectional.centerStart,
widthFactor: animation.value,
child: Opacity(
opacity: animation.value,
child: Text(
'Linting Tool',
style: textTheme.bodyText1!.copyWith(
color: AppColors.white50,
),
),
),
),
SizedBox(width: 18 * animation.value),
],
),
),
],
),
),
const SizedBox(height: 8),
],
),
);
},
);
}
}
class _NavigationRailTrailingSection extends StatelessWidget {
const _NavigationRailTrailingSection({
required this.trailingDestinations,
});
final Map<String, IconData> trailingDestinations;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final navigationRailTheme = theme.navigationRailTheme;
final animation = NavigationRail.extendedAnimation(context);
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Visibility(
maintainAnimation: true,
maintainState: true,
visible: animation.value > 0,
child: Opacity(
opacity: animation.value,
child: Align(
widthFactor: animation.value,
alignment: AlignmentDirectional.centerStart,
child: SizedBox(
height: 485,
width: 256,
child: ListView(
padding: const EdgeInsets.all(12),
physics: const NeverScrollableScrollPhysics(),
children: [
const Divider(
color: AppColors.blue200,
thickness: 0.4,
indent: 14,
endIndent: 16,
),
const SizedBox(height: 8),
for (var item in trailingDestinations.keys)
InkWell(
borderRadius: const BorderRadius.all(
Radius.circular(36),
),
onTap: () => _onTapped(context, item),
child: Column(
children: [
Row(
children: [
const SizedBox(width: 12),
Icon(
trailingDestinations[item],
color: AppColors.blue300,
),
const SizedBox(width: 24),
Text(
item,
style: textTheme.bodyText1!.copyWith(
color: navigationRailTheme
.unselectedLabelTextStyle!.color,
),
),
const SizedBox(height: 72),
],
),
],
),
),
],
),
),
),
),
);
},
);
}
void _onTapped(BuildContext context, String key) {
switch (key) {
case 'About':
showAboutDialog(
context: context,
applicationIcon: const FlutterLogo(),
children: [
const Text(
'A tool that helps you manage linter rules for your Flutter projects.',
),
],
);
break;
default:
break;
}
}
}
class _Destination {
const _Destination({
required this.destination,
required this.textLabel,
required this.icon,
this.selectedIcon,
});
final String textLabel;
final IconData icon;
final IconData? selectedIcon;
final Widget destination;
}