Skip to content

Instantly share code, notes, and snippets.

@rydmike
Last active September 4, 2022 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rydmike/78c1fa0d845892e1cf8ea8b58adbe576 to your computer and use it in GitHub Desktop.
Save rydmike/78c1fa0d845892e1cf8ea8b58adbe576 to your computer and use it in GitHub Desktop.
Demo app for Flutter issue #110951: SliverAppBar.large and SliverAppBar.medium do not use foreground color.
// MIT License
//
// Copyright (c) 2022 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';
// Used as M3 seed color
const Color seedColor = Color(0xFF386A20);
// Make a seed generated M3 light mode ColorScheme.
final ColorScheme lightScheme = ColorScheme.fromSeed(
brightness: Brightness.light,
seedColor: seedColor,
);
// Make a seed generated M3 dark mode ColorScheme.
final ColorScheme darkScheme = ColorScheme.fromSeed(
brightness: Brightness.dark,
seedColor: seedColor,
);
// AppBar colors set via widget properties
const Color appBarBackground = Color(0xFFE9EEB5);
const Color appBarForeground = Color(0xFF1489C0);
// Make AppBarTheme with custom foreground and background colors.
AppBarTheme appBarTheme({required ColorScheme colorScheme}) => AppBarTheme(
backgroundColor: colorScheme.tertiaryContainer,
foregroundColor: colorScheme.error,
);
// A simple custom theme
ThemeData appTheme(Brightness mode, bool useMaterial3) => ThemeData.from(
colorScheme: mode == Brightness.light ? lightScheme : darkScheme,
useMaterial3: useMaterial3,
).copyWith(
appBarTheme: appBarTheme(
colorScheme: mode == Brightness.light ? lightScheme : darkScheme,
),
);
void main() {
runApp(const IssueDemoApp());
}
class IssueDemoApp extends StatefulWidget {
const IssueDemoApp({super.key});
@override
State<IssueDemoApp> createState() => _IssueDemoAppState();
}
class _IssueDemoAppState extends State<IssueDemoApp> {
bool useMaterial3 = true;
ThemeMode themeMode = ThemeMode.light;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
themeMode: themeMode,
theme: appTheme(Brightness.light, useMaterial3),
darkTheme: appTheme(Brightness.dark, useMaterial3),
home: Scaffold(
appBar: AppBar(
title: const Text(('AppBar Issue Demo')),
actions: [
IconButton(
icon: useMaterial3
? const Icon(Icons.filter_3)
: const Icon(Icons.filter_2),
onPressed: () {
setState(() {
useMaterial3 = !useMaterial3;
});
},
tooltip: 'Switch to Material ${useMaterial3 ? 2 : 3}',
),
IconButton(
icon: themeMode == ThemeMode.dark
? const Icon(Icons.wb_sunny_outlined)
: const Icon(Icons.wb_sunny),
onPressed: () {
setState(() {
if (themeMode == ThemeMode.light) {
themeMode = ThemeMode.dark;
} else {
themeMode = ThemeMode.light;
}
});
},
tooltip: "Toggle brightness",
),
],
),
body: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({
super.key,
});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
const SizedBox(height: 8),
Text(
'AppBar Issue',
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
'Foreground color on SliverAppBar.medium and SliverAppBar.large are '
'not respected via AppBarTheme or AppBar properties.',
style: Theme.of(context).textTheme.bodyLarge,
),
const Divider(),
const ShowAppBarColors(),
const SizedBox(height: 8),
const AppBarShowcase(),
const Divider(),
const ShowAppBarColors(
foregroundColor: appBarForeground,
backgroundColor: appBarBackground,
),
const SizedBox(height: 8),
const AppBarShowcase(
foregroundColor: appBarForeground,
backgroundColor: appBarBackground,
),
],
);
}
}
class AppBarShowcase extends StatelessWidget {
const AppBarShowcase({super.key, this.foregroundColor, this.backgroundColor});
final Color? foregroundColor;
final Color? backgroundColor;
@override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeBottom: true,
removeTop: true,
child: Column(
children: <Widget>[
AppBar(
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: const Text('Normal AppBar'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
],
),
const SizedBox(height: 8),
CustomScrollView(
// Normally avoid shrinkWrap, but for showing a few demo
// widgets here, we can get away with it.
shrinkWrap: true,
slivers: <Widget>[
SliverAppBar(
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: const Text('Sliver AppBar'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
],
),
const SliverToBoxAdapter(child: SizedBox(height: 8)),
SliverAppBar.medium(
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: const Text('SliverAppBar.medium'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
],
),
const SliverToBoxAdapter(child: SizedBox(height: 8)),
SliverAppBar.large(
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
title: const Text('SliverAppBar.large'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
],
)
],
),
],
),
);
}
}
/// Draw a number of boxes showing the colors of key theme color properties
/// in the ColorScheme of the inherited ThemeData and its color properties.
class ShowAppBarColors extends StatelessWidget {
const ShowAppBarColors({
super.key,
this.foregroundColor,
this.backgroundColor,
});
final Color? foregroundColor;
final Color? backgroundColor;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final bool useMaterial3 = theme.useMaterial3;
const double spacing = 6;
final String colorSource =
foregroundColor == null ? ' via AppBarTheme' : ' via AppBar properties';
// Grab the card border from the theme card shape
ShapeBorder? border = theme.cardTheme.shape;
// If we had one, copy in a border side to it.
if (border is RoundedRectangleBorder) {
border = border.copyWith(
side: BorderSide(
color: theme.dividerColor,
width: 1,
),
);
// If
} else {
// If border was null, make one matching Card default, but with border
// side, if it was not null, we leave it as it was.
border ??= RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(useMaterial3 ? 12 : 4)),
side: BorderSide(
color: theme.dividerColor,
width: 1,
),
);
}
// Wrap this widget branch in a custom theme where card has a border outline
// if it did not have one, but retains in ambient themed border radius.
return Theme(
data: Theme.of(context).copyWith(
cardTheme: CardTheme.of(context).copyWith(
elevation: 0,
shape: border,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
'AppBar Colors$colorSource',
style: theme.textTheme.titleLarge,
),
),
Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: spacing,
runSpacing: spacing,
children: <Widget>[
ColorCard(
label: 'AppBar\nBackground',
color: backgroundColor ?? theme.appBarTheme.backgroundColor!,
textColor:
foregroundColor ?? theme.appBarTheme.foregroundColor!,
),
ColorCard(
label: 'AppBar\nForeground',
color: foregroundColor ?? theme.appBarTheme.foregroundColor!,
textColor:
backgroundColor ?? theme.appBarTheme.backgroundColor!,
),
],
),
],
),
);
}
}
/// A [SizedBox] with a [Card] and string text in it. Used in this demo to
/// display theme color boxes.
///
/// Can specify label text color and background color.
class ColorCard extends StatelessWidget {
const ColorCard({
super.key,
required this.label,
required this.color,
required this.textColor,
this.size,
});
final String label;
final Color color;
final Color textColor;
final Size? size;
@override
Widget build(BuildContext context) {
const double fontSize = 11;
const Size effectiveSize = Size(86, 58);
return SizedBox(
width: effectiveSize.width,
height: effectiveSize.height,
child: Card(
margin: EdgeInsets.zero,
clipBehavior: Clip.antiAlias,
color: color,
child: Center(
child: Text(
label,
style: TextStyle(color: textColor, fontSize: fontSize),
textAlign: TextAlign.center,
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment