Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions packages/flutter/lib/src/material/text_selection_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show listEquals;
import 'package:flutter/rendering.dart';

import 'color_scheme.dart';
import 'debug.dart';
import 'icon_button.dart';
import 'icons.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'theme.dart';

const double _kToolbarHeight = 44.0;
const double _kToolbarContentDistance = 8.0;
Expand Down Expand Up @@ -650,13 +652,34 @@ class _TextSelectionToolbarContainer extends StatelessWidget {

final Widget child;

// These colors were taken from a screenshot of a Pixel 6 emulator running
// Android API level 34.
static const Color _defaultColorLight = Color(0xffffffff);
static const Color _defaultColorDark = Color(0xff424242);

static Color _getColor(ColorScheme colorScheme) {
final bool isDefaultSurface = switch (colorScheme.brightness) {
Brightness.light => identical(ThemeData().colorScheme.surface, colorScheme.surface),
Brightness.dark => identical(ThemeData.dark().colorScheme.surface, colorScheme.surface),
};
if (!isDefaultSurface) {
return colorScheme.surface;
}
return switch (colorScheme.brightness) {
Brightness.light => _defaultColorLight,
Brightness.dark => _defaultColorDark,
};
}

@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Material(
// This value was eyeballed to match the native text selection menu on
// a Pixel 2 running Android 10.
borderRadius: const BorderRadius.all(Radius.circular(7.0)),
// a Pixel 6 emulator running Android API level 34.
borderRadius: const BorderRadius.all(Radius.circular(_kToolbarHeight / 2)),
clipBehavior: Clip.antiAlias,
color: _getColor(theme.colorScheme),
elevation: 1.0,
type: MaterialType.card,
child: child,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import 'package:flutter/widgets.dart';

import 'colors.dart';
import 'color_scheme.dart';
import 'constants.dart';
import 'text_button.dart';
import 'theme.dart';
Expand Down Expand Up @@ -130,20 +130,40 @@ class TextSelectionToolbarTextButton extends StatelessWidget {
);
}

// These colors were taken from a screenshot of a Pixel 6 emulator running
// Android API level 34.
static const Color _defaultForegroundColorLight = Color(0xff000000);
static const Color _defaultForegroundColorDark = Color(0xffffffff);

static Color _getForegroundColor(ColorScheme colorScheme) {
final bool isDefaultOnSurface = switch (colorScheme.brightness) {
Brightness.light => identical(ThemeData().colorScheme.onSurface, colorScheme.onSurface),
Brightness.dark => identical(ThemeData.dark().colorScheme.onSurface, colorScheme.onSurface),
};
if (!isDefaultOnSurface) {
return colorScheme.onSurface;
}
return switch (colorScheme.brightness) {
Brightness.light => _defaultForegroundColorLight,
Brightness.dark => _defaultForegroundColorDark,
};
}

@override
Widget build(BuildContext context) {
// TODO(hansmuller): Should be colorScheme.onSurface
final ThemeData theme = Theme.of(context);
final bool isDark = theme.colorScheme.brightness == Brightness.dark;
final Color foregroundColor = isDark ? Colors.white : Colors.black87;

final ColorScheme colorScheme = Theme.of(context).colorScheme;
return TextButton(
style: TextButton.styleFrom(
foregroundColor: foregroundColor,
foregroundColor: _getForegroundColor(colorScheme),
shape: const RoundedRectangleBorder(),
minimumSize: const Size(kMinInteractiveDimension, kMinInteractiveDimension),
padding: padding,
alignment: alignment,
textStyle: const TextStyle(
// This value was eyeballed from a screenshot of a Pixel 6 emulator
// running Android API level 34.
fontWeight: FontWeight.w400,
),
),
onPressed: onPressed,
child: child,
Expand Down
89 changes: 89 additions & 0 deletions packages/flutter/test/material/text_selection_toolbar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,93 @@ void main() {
expect(find.text('Paste'), findsNothing);
expect(find.text('Select all'), findsNothing);
}, skip: kIsWeb); // [intended] We don't show the toolbar on the web.

for (final ColorScheme colorScheme in <ColorScheme>[ThemeData.light().colorScheme, ThemeData.dark().colorScheme]) {
testWidgetsWithLeakTracking('default background color', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: colorScheme,
),
home: Scaffold(
body: Center(
child: TextSelectionToolbar(
anchorAbove: Offset.zero,
anchorBelow: Offset.zero,
children: <Widget>[
TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(0, 1),
onPressed: () {},
child: const Text('Custom button'),
),
],
),
),
),
),
);

Finder findToolbarContainer() {
return find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionToolbarContainer'),
matching: find.byType(Material),
);
}
expect(findToolbarContainer(), findsAtLeastNWidgets(1));

final Material toolbarContainer = tester.widget(findToolbarContainer().first);
expect(
toolbarContainer.color,
// The default colors are hardcoded and don't take the default value of
// the theme's surface color.
switch (colorScheme.brightness) {
Brightness.light => const Color(0xffffffff),
Brightness.dark => const Color(0xff424242),
},
);
});

testWidgetsWithLeakTracking('custom background color', (WidgetTester tester) async {
const Color customBackgroundColor = Colors.red;

await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: colorScheme.copyWith(
surface: customBackgroundColor,
),
),
home: Scaffold(
body: Center(
child: TextSelectionToolbar(
anchorAbove: Offset.zero,
anchorBelow: Offset.zero,
children: <Widget>[
TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(0, 1),
onPressed: () {},
child: const Text('Custom button'),
),
],
),
),
),
),
);

Finder findToolbarContainer() {
return find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionToolbarContainer'),
matching: find.byType(Material),
);
}
expect(findToolbarContainer(), findsAtLeastNWidgets(1));

final Material toolbarContainer = tester.widget(findToolbarContainer().first);
expect(
toolbarContainer.color,
customBackgroundColor,
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,67 @@ void main() {
expect(onlySize.width, greaterThan(firstSize.width));
expect(onlySize.width, greaterThan(lastSize.width));
});

for (final ColorScheme colorScheme in <ColorScheme>[ThemeData.light().colorScheme, ThemeData.dark().colorScheme]) {
testWidgetsWithLeakTracking('foreground color by default', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: colorScheme,
),
home: Scaffold(
body: Center(
child: TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(0, 1),
child: const Text('button'),
),
),
),
),
);

expect(find.byType(TextButton), findsOneWidget);

final TextButton textButton = tester.widget(find.byType(TextButton));
// The foreground color is hardcoded to black or white by default, not the
// default value from ColorScheme.onSurface.
expect(
textButton.style!.foregroundColor!.resolve(<MaterialState>{}),
switch (colorScheme.brightness) {
Brightness.light => const Color(0xff000000),
Brightness.dark => const Color(0xffffffff),
},
);
});

testWidgetsWithLeakTracking('custom foreground color', (WidgetTester tester) async {
const Color customForegroundColor = Colors.red;

await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: colorScheme.copyWith(
onSurface: customForegroundColor,
),
),
home: Scaffold(
body: Center(
child: TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(0, 1),
child: const Text('button'),
),
),
),
),
);

expect(find.byType(TextButton), findsOneWidget);

final TextButton textButton = tester.widget(find.byType(TextButton));
expect(
textButton.style!.foregroundColor!.resolve(<MaterialState>{}),
customForegroundColor,
);
});
}
}