Skip to content

Commit 1daac1b

Browse files
Fix: selection handles do not inherit color from local Theme widget (#142476)
This change uses `CapturedTheme`s to capture the themes from the context the selection handles were built in and wraps the handles with them so they can correctly inherit `Theme`s from local `Theme` widgets. `CapturedTheme`s only captures `InheritedTheme`s, so this change also makes `_InheritedCupertinoTheme` an `InheritedTheme`. This is so we can capture themes declared under a `CupertinoTheme`, for example `primaryColor` is used as the selection handle color. Fixes #74890
1 parent bd71503 commit 1daac1b

File tree

4 files changed

+105
-5
lines changed

4 files changed

+105
-5
lines changed

packages/flutter/lib/src/cupertino/theme.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,19 @@ class CupertinoTheme extends StatelessWidget {
130130
}
131131
}
132132

133-
class _InheritedCupertinoTheme extends InheritedWidget {
133+
class _InheritedCupertinoTheme extends InheritedTheme {
134134
const _InheritedCupertinoTheme({
135135
required this.theme,
136136
required super.child,
137137
});
138138

139139
final CupertinoTheme theme;
140140

141+
@override
142+
Widget wrap(BuildContext context, Widget child) {
143+
return CupertinoTheme(data: theme.data, child: child);
144+
}
145+
141146
@override
142147
bool updateShouldNotify(_InheritedCupertinoTheme old) => theme.data != old.theme.data;
143148
}

packages/flutter/lib/src/widgets/text_selection.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'debug.dart';
2121
import 'editable_text.dart';
2222
import 'framework.dart';
2323
import 'gesture_detector.dart';
24+
import 'inherited_theme.dart';
2425
import 'magnifier.dart';
2526
import 'overlay.dart';
2627
import 'scrollable.dart';
@@ -1375,12 +1376,22 @@ class SelectionOverlay {
13751376
return;
13761377
}
13771378

1379+
final OverlayState overlay = Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor);
1380+
1381+
final CapturedThemes capturedThemes = InheritedTheme.capture(
1382+
from: context,
1383+
to: overlay.context,
1384+
);
1385+
13781386
_handles = (
1379-
start: OverlayEntry(builder: _buildStartHandle),
1380-
end: OverlayEntry(builder: _buildEndHandle),
1387+
start: OverlayEntry(builder: (BuildContext context) {
1388+
return capturedThemes.wrap(_buildStartHandle(context));
1389+
}),
1390+
end: OverlayEntry(builder: (BuildContext context) {
1391+
return capturedThemes.wrap(_buildEndHandle(context));
1392+
}),
13811393
);
1382-
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)
1383-
.insertAll(<OverlayEntry>[_handles!.start, _handles!.end]);
1394+
overlay.insertAll(<OverlayEntry>[_handles!.start, _handles!.end]);
13841395
}
13851396

13861397
/// {@template flutter.widgets.SelectionOverlay.hideHandles}

packages/flutter/test/cupertino/text_field_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,47 @@ void main() {
621621
},
622622
);
623623

624+
testWidgets('selection handles color respects CupertinoTheme', (WidgetTester tester) async {
625+
// Regression test for https://github.com/flutter/flutter/issues/74890.
626+
const Color expectedSelectionHandleColor = Color.fromARGB(255, 10, 200, 255);
627+
628+
final TextEditingController controller = TextEditingController(text: 'Some text.');
629+
630+
await tester.pumpWidget(
631+
CupertinoApp(
632+
theme: const CupertinoThemeData(
633+
primaryColor: Colors.red,
634+
),
635+
home: Center(
636+
child: CupertinoTheme(
637+
data: const CupertinoThemeData(
638+
primaryColor: expectedSelectionHandleColor,
639+
),
640+
child: CupertinoTextField(controller: controller),
641+
),
642+
),
643+
),
644+
);
645+
646+
await tester.tapAt(textOffsetToPosition(tester, 0));
647+
await tester.pump();
648+
await tester.tapAt(textOffsetToPosition(tester, 0));
649+
await tester.pumpAndSettle();
650+
final Iterable<RenderBox> boxes = tester.renderObjectList<RenderBox>(
651+
find.descendant(
652+
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_SelectionHandleOverlay'),
653+
matching: find.byType(CustomPaint),
654+
),
655+
);
656+
expect(boxes.length, 2);
657+
658+
for (final RenderBox box in boxes) {
659+
expect(box, paints..path(color: expectedSelectionHandleColor));
660+
}
661+
},
662+
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
663+
);
664+
624665
testWidgets(
625666
'uses DefaultSelectionStyle for selection and cursor colors if provided',
626667
(WidgetTester tester) async {

packages/flutter/test/material/text_field_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9292,6 +9292,49 @@ void main() {
92929292
expect(editableText.style.color, isNull);
92939293
});
92949294

9295+
testWidgets('selection handles color respects Theme', (WidgetTester tester) async {
9296+
// Regression test for https://github.com/flutter/flutter/issues/74890.
9297+
const Color expectedSelectionHandleColor = Color.fromARGB(255, 10, 200, 255);
9298+
9299+
final TextEditingController controller = TextEditingController(text: 'Some text.');
9300+
9301+
await tester.pumpWidget(
9302+
MaterialApp(
9303+
theme: ThemeData(
9304+
textSelectionTheme: const TextSelectionThemeData(
9305+
selectionHandleColor: Colors.red,
9306+
),
9307+
),
9308+
home: Material(
9309+
child: Theme(
9310+
data: ThemeData(
9311+
textSelectionTheme: const TextSelectionThemeData(
9312+
selectionHandleColor: expectedSelectionHandleColor,
9313+
),
9314+
),
9315+
child: TextField(controller: controller),
9316+
),
9317+
),
9318+
),
9319+
);
9320+
9321+
await tester.longPressAt(textOffsetToPosition(tester, 0));
9322+
await tester.pumpAndSettle();
9323+
final Iterable<RenderBox> boxes = tester.renderObjectList<RenderBox>(
9324+
find.descendant(
9325+
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_SelectionHandleOverlay'),
9326+
matching: find.byType(CustomPaint),
9327+
),
9328+
);
9329+
expect(boxes.length, 2);
9330+
9331+
for (final RenderBox box in boxes) {
9332+
expect(box, paints..path(color: expectedSelectionHandleColor));
9333+
}
9334+
},
9335+
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia }),
9336+
);
9337+
92959338
testWidgets('style enforces required fields', (WidgetTester tester) async {
92969339
Widget buildFrame(TextStyle style) {
92979340
return MaterialApp(

0 commit comments

Comments
 (0)