feat(ui): in dark mode, filled and floating buttons in cobalt itself, white on it
In dark mode the primary colour is cobalt lightened (#AEB9FF), readable as text and thin marks on the dark surfaces, and filled buttons took it: Todo's send button glared bright beside everything else; floating buttons took Material's dark primary container. Both are cobalt itself now, white on it, as in light mode (decision 6 of the developer's list, 9 Nov 2026); links, tabs and switches keep the lighter tone. Filled icon buttons take no theme of their own in Flutter (the icon buttons' theme is the plain ones' too), so they take filledIconButtonStyle: Learn's study and send buttons, Todo's send buttons and Locate me, Meditate's stop dictating. Whats-New: In dark mode, filled buttons and the + button are cobalt with a white icon, no longer a bright light blue. Whats-New-De: Im dunklen Modus sind gefüllte Schaltflächen und die +-Schaltfläche kobaltblau mit weißem Symbol statt grell hellblau. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
83c61b8d81
commit
cc7511f060
2 changed files with 122 additions and 2 deletions
|
|
@ -8,8 +8,9 @@ import 'package:material_ui/material_ui.dart';
|
|||
/// colour scheme around cobalt and copper, Halkyon's font
|
||||
/// ([appFontFamily]), [ThemeExtras] and the app's own [extensions] beside
|
||||
/// them, the current style of progress indicators, Material 3 Expressive's
|
||||
/// shorter navigation bar, and snack bars floating above it
|
||||
/// ([FittedSnackBars]).
|
||||
/// shorter navigation bar, snack bars floating above it
|
||||
/// ([FittedSnackBars]), and in dark mode filled and floating buttons in
|
||||
/// cobalt itself ([filledIconButtonStyle] for filled icon buttons).
|
||||
ThemeData appTheme(
|
||||
Brightness brightness, {
|
||||
Iterable<ThemeExtension<dynamic>> extensions = const [],
|
||||
|
|
@ -71,6 +72,22 @@ ThemeData _baseTheme(
|
|||
behavior: SnackBarBehavior.floating,
|
||||
insetPadding: EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
),
|
||||
// In dark mode the primary colour is cobalt lightened, readable as text
|
||||
// and thin marks on the dark surfaces (links, tabs, switches); a filled
|
||||
// button or a floating one in it glared, bright beside everything else.
|
||||
// They are cobalt itself, white on it (the developer's review, 9 Nov
|
||||
// 2026), as in light mode; disabled, Material's own.
|
||||
filledButtonTheme: switch (brightness) {
|
||||
Brightness.light => null,
|
||||
Brightness.dark => FilledButtonThemeData(style: _cobaltFill),
|
||||
},
|
||||
floatingActionButtonTheme: switch (brightness) {
|
||||
Brightness.light => null,
|
||||
Brightness.dark => const FloatingActionButtonThemeData(
|
||||
backgroundColor: Brand.primary,
|
||||
foregroundColor: _white,
|
||||
),
|
||||
},
|
||||
extensions: [
|
||||
switch (brightness) {
|
||||
Brightness.light => ThemeExtras.light,
|
||||
|
|
@ -92,6 +109,27 @@ ThemeData _baseTheme(
|
|||
|
||||
const _white = Color(0xFFFFFFFF);
|
||||
|
||||
/// Cobalt itself, white on it, but where disabled: Material's own there.
|
||||
final _cobaltFill = ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith(
|
||||
(states) => states.contains(WidgetState.disabled) ? null : Brand.primary,
|
||||
),
|
||||
foregroundColor: WidgetStateProperty.resolveWith(
|
||||
(states) => states.contains(WidgetState.disabled) ? null : _white,
|
||||
),
|
||||
);
|
||||
|
||||
/// A filled icon button's style (`IconButton.filled(style: …)`), as
|
||||
/// [appTheme] gives filled buttons: in dark mode cobalt itself, white on it;
|
||||
/// none in light mode, where the primary colour is cobalt already. Filled
|
||||
/// icon buttons take no theme of their own: the icon buttons' theme is every
|
||||
/// icon button's, the plain ones' too.
|
||||
ButtonStyle? filledIconButtonStyle(BuildContext context) =>
|
||||
switch (Theme.of(context).brightness) {
|
||||
Brightness.light => null,
|
||||
Brightness.dark => _cobaltFill,
|
||||
};
|
||||
|
||||
ColorScheme _lightScheme() =>
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: Brand.primary,
|
||||
|
|
|
|||
|
|
@ -92,6 +92,81 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
group('dark: filled buttons and floating buttons in cobalt itself, white '
|
||||
"on it (the developer's review, 9 Nov 2026)", () {
|
||||
final theme = appTheme(Brightness.dark);
|
||||
const none = <WidgetState>{};
|
||||
|
||||
test('a filled button', () {
|
||||
final style = theme.filledButtonTheme.style!;
|
||||
expect(style.backgroundColor!.resolve(none), Brand.primary);
|
||||
expect(style.foregroundColor!.resolve(none), Colors.white);
|
||||
expect(
|
||||
style.backgroundColor!.resolve({WidgetState.disabled}),
|
||||
isNot(Brand.primary),
|
||||
reason: "disabled, Material's own",
|
||||
);
|
||||
});
|
||||
|
||||
test('a floating button', () {
|
||||
expect(theme.floatingActionButtonTheme.backgroundColor, Brand.primary);
|
||||
expect(theme.floatingActionButtonTheme.foregroundColor, Colors.white);
|
||||
});
|
||||
|
||||
test('readable: white on cobalt', () {
|
||||
expect(_contrast(Colors.white, Brand.primary), greaterThan(7));
|
||||
});
|
||||
|
||||
test("links, tabs and switches keep the primary colour's lighter "
|
||||
'tone', () {
|
||||
expect(theme.colorScheme.primary, isNot(Brand.primary));
|
||||
});
|
||||
|
||||
testWidgets('a filled icon button, styled by filledIconButtonStyle', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Builder(
|
||||
builder: (context) => IconButton.filled(
|
||||
style: filledIconButtonStyle(context),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.arrow_upward),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final material = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byType(IconButton),
|
||||
matching: find.byType(Material),
|
||||
),
|
||||
);
|
||||
expect(material.color, Brand.primary);
|
||||
expect(
|
||||
tester
|
||||
.widget<IconTheme>(
|
||||
find
|
||||
.ancestor(
|
||||
of: find.byIcon(Icons.arrow_upward),
|
||||
matching: find.byType(IconTheme),
|
||||
)
|
||||
.first,
|
||||
)
|
||||
.data
|
||||
.color,
|
||||
Colors.white,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('light: the buttons as Material 3 has them', () {
|
||||
final theme = appTheme(Brightness.light);
|
||||
expect(theme.filledButtonTheme.style, isNull);
|
||||
expect(theme.floatingActionButtonTheme.backgroundColor, isNull);
|
||||
});
|
||||
|
||||
test("the navigation bar is Material 3 Expressive's shorter one", () {
|
||||
expect(appTheme(Brightness.light).navigationBarTheme.height, 64);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue