mirror of https://github.com/flutter/samples.git
[linting_tool] Add Adaptive Layout with Theming (#852)
parent
35f1670098
commit
8e73c73f4b
@ -0,0 +1,40 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
import 'package:linting_tool/theme/app_theme.dart';
|
||||||
|
import 'package:linting_tool/widgets/adaptive_nav.dart';
|
||||||
|
import 'package:linting_tool/routes.dart' as routes;
|
||||||
|
|
||||||
|
class LintingTool extends StatefulWidget {
|
||||||
|
const LintingTool({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
static const String homeRoute = routes.homeRoute;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LintingToolState createState() => _LintingToolState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LintingToolState extends State<LintingTool> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Flutter Linting Tool',
|
||||||
|
theme: AppTheme.buildReplyLightTheme(context),
|
||||||
|
darkTheme: AppTheme.buildReplyDarkTheme(context),
|
||||||
|
themeMode: ThemeMode.light,
|
||||||
|
initialRoute: LintingTool.homeRoute,
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
switch (settings.name) {
|
||||||
|
case LintingTool.homeRoute:
|
||||||
|
return MaterialPageRoute<void>(
|
||||||
|
builder: (context) => const AdaptiveNav(),
|
||||||
|
settings: settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
// 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 'package:adaptive_breakpoints/adaptive_breakpoints.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Returns a boolean value whether the window is considered medium or large size.
|
||||||
|
/// Used to build adaptive and responsive layouts.
|
||||||
|
bool isDisplayLarge(BuildContext context) =>
|
||||||
|
getWindowType(context) >= AdaptiveWindowType.medium;
|
||||||
|
|
||||||
|
/// Returns boolean value whether the window is considered medium size.
|
||||||
|
/// Used to build adaptive and responsive layouts.
|
||||||
|
bool isDisplayMedium(BuildContext context) {
|
||||||
|
return getWindowType(context) == AdaptiveWindowType.medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns boolean value whether the window is considered small size.
|
||||||
|
/// Used to build adaptive and responsive layouts.
|
||||||
|
bool isDisplaySmall(BuildContext context) {
|
||||||
|
return getWindowType(context) <= AdaptiveWindowType.small;
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DefaultLintsPage extends StatelessWidget {
|
||||||
|
const DefaultLintsPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// TODO(abd99): Implement DefaultLintsPage, showing a list of default lint rules.
|
||||||
|
return const Text('Default Profiles');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
const HomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// TODO(abd99): Implement HomePage, showing a list of supported lint rules.
|
||||||
|
return const Text('Home');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SavedLintsPage extends StatelessWidget {
|
||||||
|
const SavedLintsPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// TODO(abd99): Implement SavedLintsPage, showing a list of saved lint rules profiles.
|
||||||
|
return const Text('Saved Profiles');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
const String homeRoute = '/home';
|
||||||
|
// TODO(abd99): Add new routes.
|
@ -0,0 +1,208 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:linting_tool/theme/colors.dart';
|
||||||
|
|
||||||
|
class AppTheme {
|
||||||
|
static ThemeData buildReplyLightTheme(BuildContext context) {
|
||||||
|
final base = ThemeData.light();
|
||||||
|
return base.copyWith(
|
||||||
|
bottomAppBarColor: AppColors.blue700,
|
||||||
|
bottomSheetTheme: BottomSheetThemeData(
|
||||||
|
backgroundColor: AppColors.blue700,
|
||||||
|
modalBackgroundColor: Colors.white.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
navigationRailTheme: NavigationRailThemeData(
|
||||||
|
backgroundColor: AppColors.blue700,
|
||||||
|
selectedIconTheme: const IconThemeData(color: AppColors.orange500),
|
||||||
|
selectedLabelTextStyle:
|
||||||
|
GoogleFonts.workSansTextTheme().headline5!.copyWith(
|
||||||
|
color: AppColors.orange500,
|
||||||
|
),
|
||||||
|
unselectedIconTheme: const IconThemeData(color: AppColors.blue200),
|
||||||
|
unselectedLabelTextStyle:
|
||||||
|
GoogleFonts.workSansTextTheme().headline5!.copyWith(
|
||||||
|
color: AppColors.blue200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
canvasColor: AppColors.white50,
|
||||||
|
cardColor: AppColors.white50,
|
||||||
|
chipTheme: _buildChipTheme(
|
||||||
|
AppColors.blue700,
|
||||||
|
AppColors.lightChipBackground,
|
||||||
|
Brightness.light,
|
||||||
|
),
|
||||||
|
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blueGrey),
|
||||||
|
textTheme: _buildReplyLightTextTheme(base.textTheme),
|
||||||
|
scaffoldBackgroundColor: AppColors.blue50,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ThemeData buildReplyDarkTheme(BuildContext context) {
|
||||||
|
final base = ThemeData.dark();
|
||||||
|
return base.copyWith(
|
||||||
|
bottomAppBarColor: AppColors.darkBottomAppBarBackground,
|
||||||
|
bottomSheetTheme: BottomSheetThemeData(
|
||||||
|
backgroundColor: AppColors.darkDrawerBackground,
|
||||||
|
modalBackgroundColor: Colors.black.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
navigationRailTheme: NavigationRailThemeData(
|
||||||
|
backgroundColor: AppColors.darkBottomAppBarBackground,
|
||||||
|
selectedIconTheme: const IconThemeData(color: AppColors.orange300),
|
||||||
|
selectedLabelTextStyle:
|
||||||
|
GoogleFonts.workSansTextTheme().headline5!.copyWith(
|
||||||
|
color: AppColors.orange300,
|
||||||
|
),
|
||||||
|
unselectedIconTheme: const IconThemeData(color: AppColors.greyLabel),
|
||||||
|
unselectedLabelTextStyle:
|
||||||
|
GoogleFonts.workSansTextTheme().headline5!.copyWith(
|
||||||
|
color: AppColors.greyLabel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
canvasColor: AppColors.black900,
|
||||||
|
cardColor: AppColors.darkCardBackground,
|
||||||
|
chipTheme: _buildChipTheme(
|
||||||
|
AppColors.blue200,
|
||||||
|
AppColors.darkChipBackground,
|
||||||
|
Brightness.dark,
|
||||||
|
),
|
||||||
|
colorScheme: const ColorScheme.dark(
|
||||||
|
primary: AppColors.blue200,
|
||||||
|
primaryVariant: AppColors.blue300,
|
||||||
|
secondary: AppColors.orange300,
|
||||||
|
secondaryVariant: AppColors.orange300,
|
||||||
|
surface: AppColors.black800,
|
||||||
|
error: AppColors.red200,
|
||||||
|
onPrimary: AppColors.black900,
|
||||||
|
onSecondary: AppColors.black900,
|
||||||
|
onBackground: AppColors.white50,
|
||||||
|
onSurface: AppColors.white50,
|
||||||
|
onError: AppColors.black900,
|
||||||
|
background: AppColors.black900Alpha087,
|
||||||
|
),
|
||||||
|
textTheme: _buildReplyDarkTextTheme(base.textTheme),
|
||||||
|
scaffoldBackgroundColor: AppColors.black900,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ChipThemeData _buildChipTheme(
|
||||||
|
Color primaryColor,
|
||||||
|
Color chipBackground,
|
||||||
|
Brightness brightness,
|
||||||
|
) {
|
||||||
|
return ChipThemeData(
|
||||||
|
backgroundColor: primaryColor.withOpacity(0.12),
|
||||||
|
disabledColor: primaryColor.withOpacity(0.87),
|
||||||
|
selectedColor: primaryColor.withOpacity(0.05),
|
||||||
|
secondarySelectedColor: chipBackground,
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
shape: const StadiumBorder(),
|
||||||
|
labelStyle: GoogleFonts.workSansTextTheme().bodyText2!.copyWith(
|
||||||
|
color: brightness == Brightness.dark
|
||||||
|
? AppColors.white50
|
||||||
|
: AppColors.black900,
|
||||||
|
),
|
||||||
|
secondaryLabelStyle: GoogleFonts.workSansTextTheme().bodyText2!,
|
||||||
|
brightness: brightness,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextTheme _buildReplyLightTextTheme(TextTheme base) {
|
||||||
|
return base.copyWith(
|
||||||
|
headline4: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 34,
|
||||||
|
letterSpacing: 0.4,
|
||||||
|
height: 0.9,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
headline5: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24,
|
||||||
|
letterSpacing: 0.27,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
headline6: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 20,
|
||||||
|
letterSpacing: 0.18,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
subtitle2: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
letterSpacing: -0.04,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
bodyText1: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
bodyText2: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 14,
|
||||||
|
letterSpacing: -0.05,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
caption: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 12,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
color: AppColors.black900,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextTheme _buildReplyDarkTextTheme(TextTheme base) {
|
||||||
|
return base.copyWith(
|
||||||
|
headline4: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 34,
|
||||||
|
letterSpacing: 0.4,
|
||||||
|
height: 0.9,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
headline5: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24,
|
||||||
|
letterSpacing: 0.27,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
headline6: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 20,
|
||||||
|
letterSpacing: 0.18,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
subtitle2: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
letterSpacing: -0.04,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
bodyText1: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
bodyText2: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 14,
|
||||||
|
letterSpacing: -0.05,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
caption: GoogleFonts.workSans(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 12,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
color: AppColors.white50,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AppColors {
|
||||||
|
static const Color white50 = Color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
static const Color black800 = Color(0xFF121212);
|
||||||
|
static const Color black900 = Color(0xFF000000);
|
||||||
|
|
||||||
|
static const Color blue50 = Color(0xFFEEF0F2);
|
||||||
|
static const Color blue100 = Color(0xFFD2DBE0);
|
||||||
|
static const Color blue200 = Color(0xFFADBBC4);
|
||||||
|
static const Color blue300 = Color(0xFF8CA2AE);
|
||||||
|
static const Color blue600 = Color(0xFF4A6572);
|
||||||
|
static const Color blue700 = Color(0xFF344955);
|
||||||
|
static const Color blue800 = Color(0xFF232F34);
|
||||||
|
|
||||||
|
static const Color orange300 = Color(0xFFFBD790);
|
||||||
|
static const Color orange400 = Color(0xFFF9BE64);
|
||||||
|
static const Color orange500 = Color(0xFFF9AA33);
|
||||||
|
|
||||||
|
static const Color red200 = Color(0xFFCF7779);
|
||||||
|
static const Color red400 = Color(0xFFFF4C5D);
|
||||||
|
|
||||||
|
static const Color white50Alpha060 = Color(0x99FFFFFF);
|
||||||
|
|
||||||
|
static const Color blue50Alpha060 = Color(0x99EEF0F2);
|
||||||
|
|
||||||
|
static const Color black900Alpha020 = Color(0x33000000);
|
||||||
|
static const Color black900Alpha087 = Color(0xDE000000);
|
||||||
|
static const Color black900Alpha060 = Color(0x99000000);
|
||||||
|
|
||||||
|
static const Color greyLabel = Color(0xFFAEAEAE);
|
||||||
|
static const Color darkBottomAppBarBackground = Color(0xFF2D2D2D);
|
||||||
|
static const Color darkDrawerBackground = Color(0xFF353535);
|
||||||
|
static const Color darkCardBackground = Color(0xFF1E1E1E);
|
||||||
|
static const Color darkChipBackground = Color(0xFF2A2A2A);
|
||||||
|
static const Color lightChipBackground = Color(0xFFE5E5E5);
|
||||||
|
}
|
@ -0,0 +1,334 @@
|
|||||||
|
// 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/rendering.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/layout/adaptive.dart';
|
||||||
|
import 'package:linting_tool/theme/colors.dart';
|
||||||
|
|
||||||
|
final navKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
class AdaptiveNav extends StatefulWidget {
|
||||||
|
const AdaptiveNav({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AdaptiveNavState 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(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
final _trailing = <String, IconData>{
|
||||||
|
'About': Icons.info_outline,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _NavView(
|
||||||
|
extended: isDesktop,
|
||||||
|
destinations: _navigationDestinations,
|
||||||
|
trailing: _trailing,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavView extends StatefulWidget {
|
||||||
|
const _NavView({
|
||||||
|
Key? key,
|
||||||
|
required this.extended,
|
||||||
|
required this.destinations,
|
||||||
|
this.trailing,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final bool extended;
|
||||||
|
final List<_Destination> destinations;
|
||||||
|
final Map<String, IconData>? trailing;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_NavViewState createState() => _NavViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavViewState extends State<_NavView> {
|
||||||
|
late 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) {
|
||||||
|
return Scaffold(
|
||||||
|
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(
|
||||||
|
key: const ValueKey('ReplyLogo'),
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
platform :osx, '10.11'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_macos_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_macos_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,22 @@
|
|||||||
|
PODS:
|
||||||
|
- FlutterMacOS (1.0.0)
|
||||||
|
- path_provider_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
|
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
FlutterMacOS:
|
||||||
|
:path: Flutter/ephemeral
|
||||||
|
path_provider_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||||
|
path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
||||||
|
|
||||||
|
COCOAPODS: 1.10.1
|
Loading…
Reference in new issue