Skip to content

Instantly share code, notes, and snippets.

@rydmike
Last active September 11, 2020 22:32
Show Gist options
  • Save rydmike/607763139eab6f81159bcc3a61be1164 to your computer and use it in GitHub Desktop.
Save rydmike/607763139eab6f81159bcc3a61be1164 to your computer and use it in GitHub Desktop.
Flutter issue: Fix example: ChoiceChip() when selected looks disabled when using a dark theme
// MIT License
// Copyright 2020 Mike Rydstrom
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
// ****************************************************************************
// Const and finals for setup and theme
const double kEdgePadding = 16.0;
const double kMaxContentWidth = 1200.0;
const double kMinCardWidth = 200;
const int kMaxCards = 200;
// Custom Eden palette
// Color sources: https://www.w3schools.com/colors/colors_2019.asp
const Color lightPrimary = Color(0xFF264E36); // Eden 23%
const Color lightPrimaryVariant = Color(0xFF224430); // Eden 20%
const Color lightSecondary = Color(0xFF797b3a); // Guacamole 35%
const Color lightSecondaryVariant = Color(0xFF555729); // Guacamole 25%
final Color lightCanvas = Color.alphaBlend(
lightPrimary.withAlpha((256 * 0.07).toInt()), Colors.white);
const Color darkPrimary = Color(0xFF59a678); // Eden light 50%
const Color darkPrimaryVariant = Color(0xFF478560); // Eden light 40%
const Color darkSecondary = Color(0xFFd5d6a8); // Guacamole 75%
const Color darkSecondaryVariant = Color(0xFFbbbe74); // Guacamole 60%
final Color darkCanvas = Color.alphaBlend(
darkPrimary.withAlpha((256 * 0.12).toInt()), const Color(0xFF121212));
const ColorScheme lightScheme = ColorScheme.light(
primary: lightPrimary,
primaryVariant: lightPrimaryVariant,
secondary: lightSecondary,
secondaryVariant: lightSecondaryVariant,
);
const ColorScheme darkScheme = ColorScheme.dark(
primary: darkPrimary,
primaryVariant: darkPrimaryVariant,
secondary: darkSecondary,
secondaryVariant: darkSecondaryVariant,
);
final ThemeData lightTheme = ThemeData.from(colorScheme: lightScheme).copyWith(
canvasColor: lightCanvas,
toggleableActiveColor: lightScheme.secondary,
buttonTheme: const ButtonThemeData(
colorScheme: lightScheme,
textTheme: ButtonTextTheme.primary,
),
chipTheme: ChipThemeData.fromDefaults(
secondaryColor: lightScheme.primary,
brightness: lightScheme.brightness,
labelStyle: ThemeData.light().textTheme.bodyText1,
),
);
final ThemeData darkTheme = ThemeData.from(colorScheme: darkScheme).copyWith(
canvasColor: darkCanvas,
toggleableActiveColor: darkScheme.secondary,
buttonTheme: const ButtonThemeData(
colorScheme: darkScheme,
textTheme: ButtonTextTheme.primary,
),
);
// ****************************************************************************
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isDarkMode;
bool colorSchemeTheme;
@override
void initState() {
isDarkMode = false;
colorSchemeTheme = false;
super.initState();
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Theme.of(context).brightness,
systemNavigationBarColor: theme.scaffoldBackgroundColor,
systemNavigationBarIconBrightness: theme.brightness == Brightness.light
? Brightness.dark
: Brightness.light,
),
);
return MaterialApp(
title: 'Issue Discovery',
debugShowCheckedModeBanner: false,
theme: colorSchemeTheme
? lightTheme.copyWith(
chipTheme: ChipThemeData.fromDefaults(
secondaryColor: lightTheme.colorScheme.primary,
brightness: lightTheme.brightness,
labelStyle: lightTheme.textTheme.bodyText1,
),
)
: ThemeData.light().copyWith(
chipTheme: ChipThemeData.fromDefaults(
secondaryColor: ThemeData.light().colorScheme.primary,
brightness: ThemeData.light().brightness,
labelStyle: ThemeData.light().textTheme.bodyText1,
),
),
darkTheme: colorSchemeTheme
? darkTheme.copyWith(
chipTheme: ChipThemeData.fromDefaults(
secondaryColor: darkTheme.colorScheme.primary,
brightness: darkTheme.brightness,
labelStyle: darkTheme.textTheme.bodyText1,
),
)
: ThemeData.dark().copyWith(
chipTheme: ChipThemeData.fromDefaults(
secondaryColor: ThemeData.dark().colorScheme.primary,
brightness: ThemeData.dark().brightness,
labelStyle: ThemeData.dark().textTheme.bodyText1,
),
),
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
home: DemoPage(
title: 'Issue Discovery - Fix Example',
setDarkMode: (bool value) {
setState(() {
isDarkMode = value;
});
},
setCustomTheme: (bool value) {
setState(() {
colorSchemeTheme = value;
});
},
),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({
Key key,
this.title,
this.setDarkMode,
this.setCustomTheme,
}) : super(key: key);
final String title;
final ValueChanged<bool> setDarkMode;
final ValueChanged<bool> setCustomTheme;
@override
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
bool useCustomTheme;
bool isDarkMode;
@override
void initState() {
isDarkMode = false;
useCustomTheme = false;
super.initState();
}
@override
Widget build(BuildContext context) {
final MediaQueryData media = MediaQuery.of(context);
final double topPadding = media.padding.top;
final double bottomPadding = media.padding.bottom;
final Size size = media.size;
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
final TextStyle headline6 = textTheme.headline6;
final TextStyle caption = theme.primaryTextTheme.caption;
return Scaffold(
extendBodyBehindAppBar: true,
extendBody: true,
appBar: AppBar(
title: Row(
children: <Widget>[
Text(widget.title),
// Show canvas size in the app bar
Expanded(
child: Text(
" W:${size.width.round()} H:${size.height.round()}",
style: caption,
textAlign: TextAlign.right,
),
),
],
),
centerTitle: false,
elevation: 0,
backgroundColor: Colors.transparent,
// Gradient partially transparent AppBar
flexibleSpace: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Theme.of(context).dividerColor),
),
gradient: LinearGradient(
begin: AlignmentDirectional.centerStart,
end: AlignmentDirectional.centerEnd,
colors: <Color>[
theme.primaryColor,
theme.primaryColor.withOpacity(0.7),
],
),
),
child: null,
),
),
body: Scrollbar(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: kMaxContentWidth),
child: ListView(
padding: EdgeInsets.fromLTRB(
kEdgePadding,
topPadding + kToolbarHeight + kEdgePadding,
kEdgePadding,
kEdgePadding + bottomPadding,
),
children: <Widget>[
Text(
'ChoiceChip Dark Theme Issue - Example fix',
style: textTheme.headline4,
),
//
// Change theme
//
const Divider(),
Text('Issue', style: headline6),
const Text(
'A selected ChoiceChip when using dark themes looks more like '
'it selected and is legible.\n\n'
''
'The aplied custom theme only changes the dark mode selected '
'ChoiceChip.'),
SwitchListTile.adaptive(
title: const Text('Use dark theme'),
subtitle:
const Text('OFF for light theme, ON for dark theme.'),
value: isDarkMode,
onChanged: (bool value) {
setState(() {
isDarkMode = value;
widget.setDarkMode(isDarkMode);
});
},
),
SwitchListTile.adaptive(
title: const Text(
'Use a ColorScheme based theme',
),
subtitle: const Text(
'Turn OFF to use the default light and dark theme. '
'Turn ON to use a custom ColorScheme based example theme.',
),
value: useCustomTheme,
onChanged: (bool value) {
setState(() {
useCustomTheme = value;
widget.setCustomTheme(useCustomTheme);
});
},
),
const Divider(),
Wrap(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(8.0),
child: Chip(
label: Text('Chip'),
),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Chip(
label: Text('Chip with Avatar'),
avatar: FlutterLogo(),
),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: InputChip(
label: Text('InputChip'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ChoiceChip(
label: const Text('ChoiceChip selected'),
selected: true,
onSelected: (bool value) {},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ChoiceChip(
label: const Text('ChoiceChip not selected'),
selected: false,
onSelected: (bool value) {},
),
),
],
),
const Divider(),
Text('Source code for this demo', style: headline6),
const SelectableText(
'https://gist.github.com/rydmike/607763139eab6f81159bcc3a61be1164'),
const Divider(),
//
// A Card GridView with responsive column amount.
//
const SizedBox(height: kEdgePadding),
Text('Colorful cards in a responsive GridView',
style: headline6),
const Text('Just used as filler content in this issue demo.'),
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraint) {
final int nrOfColumns =
(constraint.maxWidth.toInt()) ~/ kMinCardWidth.toInt();
return GridView.builder(
padding: const EdgeInsets.all(kEdgePadding),
shrinkWrap: true,
primary: false,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: nrOfColumns == 0 ? 1 : nrOfColumns,
mainAxisSpacing: kEdgePadding,
crossAxisSpacing: kEdgePadding,
childAspectRatio: 1.4,
),
itemCount: kMaxCards,
itemBuilder: (_, int index) => Card(
elevation: 3,
child: GridItem(
title: 'Card ${index + 1}',
color: Colors
.primaries[index % Colors.primaries.length][
Theme.of(context).brightness == Brightness.light
? 600
: 300],
),
),
);
},
)
],
),
),
),
),
);
}
}
// The grid items for the gridview with cards, not relevant for this case,
// just in the demo app as filler to stress it a bit more during resizing.
class GridItem extends StatelessWidget {
const GridItem({Key key, this.title, this.color}) : super(key: key);
final String title;
final Color color;
@override
Widget build(BuildContext context) {
final Color textColor =
ThemeData.estimateBrightnessForColor(color) == Brightness.light
? Colors.black87
: Colors.white70;
return Container(
color: color,
padding: const EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
title,
style: TextStyle(
color: textColor,
fontSize: 18,
),
),
Icon(Icons.menu, color: textColor),
],
),
);
}
}
@rydmike
Copy link
Author

rydmike commented Sep 11, 2020

Sample code for issue demo for Flutter issue, an example of how to remedy the situation with a theme definition:
flutter/flutter#65663

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment