|
|
|
// 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.titleSmall!.copyWith(
|
|
|
|
color: textTheme.bodyLarge!.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.bodyLarge!.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.bodyLarge!.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.',
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
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;
|
|
|
|
}
|