fix(ios): correctly inherits the handle color from the theme (#166507)

## Description

If specified, apply the `selectionHandleColor` from the theme to the iOS
handle.

## Related Issue

- https://github.com/flutter/flutter/issues/166506

#### Minimum reproducible example

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        textSelectionTheme: TextSelectionThemeData(
          selectionHandleColor: Colors.yellow,
        ),
      ),
      home: Scaffold(body: Center(child: TextField())),
    ),
  );
}
```

#### Visual Reference

| Previous | Now |
|--------|--------|
| <img
src="https://github.com/user-attachments/assets/91e49e07-ef4a-47ab-b65a-b147f441f252"
/> | <img
src="https://github.com/user-attachments/assets/fd27d602-6f7c-4d7d-a310-97f417bde819"
/> |

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Ricardo Dalarme 2025-05-01 17:03:39 -03:00 committed by GitHub
parent 761566b5d8
commit a152df8357
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 84 additions and 6 deletions

View File

@ -134,3 +134,4 @@ Mohammed Chahboun <m97.chahboun@gmail.com>
Abdessalem Mohellebi <mohellebiabdessalem@gmail.com>
Jin Jeongsu <jinjs.dev@gmail.com>
Mairon Slusarz <maironlucaslusarz@gmail.com>
Ricardo Dalarme <ricardodalarme@outlook.com>

View File

@ -122,7 +122,9 @@ class CupertinoTextSelectionControls extends TextSelectionControls {
final Widget handle;
final Widget customPaint = CustomPaint(
painter: _CupertinoTextSelectionHandlePainter(CupertinoTheme.of(context).primaryColor),
painter: _CupertinoTextSelectionHandlePainter(
CupertinoTheme.of(context).selectionHandleColor,
),
);
// [buildHandle]'s widget is positioned at the selection cursor's bottom

View File

@ -29,6 +29,7 @@ const _CupertinoThemeDefaults _kDefaultTheme = _CupertinoThemeDefaults(
// Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616.
),
CupertinoColors.systemBackground,
CupertinoColors.systemBlue,
false,
_CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray),
);
@ -179,6 +180,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
Color? selectionHandleColor,
bool? applyThemeToAll,
}) : this.raw(
brightness,
@ -187,6 +189,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
selectionHandleColor,
applyThemeToAll,
);
@ -202,6 +205,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
Color? selectionHandleColor,
bool? applyThemeToAll,
) : this._rawWithDefaults(
brightness,
@ -210,6 +214,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
selectionHandleColor,
applyThemeToAll,
_kDefaultTheme,
);
@ -221,6 +226,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
Color? selectionHandleColor,
bool? applyThemeToAll,
this._defaults,
) : super(
@ -230,6 +236,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
selectionHandleColor: selectionHandleColor,
applyThemeToAll: applyThemeToAll,
);
@ -255,6 +262,9 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
Color get scaffoldBackgroundColor =>
super.scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor;
@override
Color get selectionHandleColor => super.selectionHandleColor ?? _defaults.selectionHandleColor;
@override
bool get applyThemeToAll => super.applyThemeToAll ?? _defaults.applyThemeToAll;
@ -267,6 +277,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme: super.textTheme,
barBackgroundColor: super.barBackgroundColor,
scaffoldBackgroundColor: super.scaffoldBackgroundColor,
selectionHandleColor: super.selectionHandleColor,
applyThemeToAll: super.applyThemeToAll,
);
}
@ -282,6 +293,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
super.textTheme?.resolveFrom(context),
convertColor(super.barBackgroundColor),
convertColor(super.scaffoldBackgroundColor),
convertColor(super.selectionHandleColor),
applyThemeToAll,
_defaults.resolveFrom(context, super.textTheme == null),
);
@ -295,6 +307,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
Color? selectionHandleColor,
bool? applyThemeToAll,
}) {
return CupertinoThemeData._rawWithDefaults(
@ -304,6 +317,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme ?? super.textTheme,
barBackgroundColor ?? super.barBackgroundColor,
scaffoldBackgroundColor ?? super.scaffoldBackgroundColor,
selectionHandleColor ?? super.selectionHandleColor,
applyThemeToAll ?? super.applyThemeToAll,
_defaults,
);
@ -342,6 +356,13 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
defaultValue: defaultData.scaffoldBackgroundColor,
),
);
properties.add(
createCupertinoColorProperty(
'selectionHandleColor',
selectionHandleColor,
defaultValue: defaultData.selectionHandleColor,
),
);
properties.add(
DiagnosticsProperty<bool>(
'applyThemeToAll',
@ -367,6 +388,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
other.textTheme == textTheme &&
other.barBackgroundColor == barBackgroundColor &&
other.scaffoldBackgroundColor == scaffoldBackgroundColor &&
other.selectionHandleColor == selectionHandleColor &&
other.applyThemeToAll == applyThemeToAll;
}
@ -378,6 +400,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
selectionHandleColor,
applyThemeToAll,
);
}
@ -406,6 +429,7 @@ class NoDefaultCupertinoThemeData {
this.textTheme,
this.barBackgroundColor,
this.scaffoldBackgroundColor,
this.selectionHandleColor,
this.applyThemeToAll,
});
@ -474,6 +498,11 @@ class NoDefaultCupertinoThemeData {
/// Defaults to [CupertinoColors.systemBackground].
final Color? scaffoldBackgroundColor;
/// The color of the selection handles on the text field.
///
/// Defaults to [CupertinoColors.systemBlue].
final Color? selectionHandleColor;
/// Flag to apply this theme to all descendant Cupertino widgets.
///
/// Certain Cupertino widgets previously didn't use theming, matching past
@ -513,6 +542,7 @@ class NoDefaultCupertinoThemeData {
textTheme: textTheme?.resolveFrom(context),
barBackgroundColor: convertColor(barBackgroundColor),
scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor),
selectionHandleColor: convertColor(selectionHandleColor),
applyThemeToAll: applyThemeToAll,
);
}
@ -530,6 +560,7 @@ class NoDefaultCupertinoThemeData {
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
Color? selectionHandleColor,
bool? applyThemeToAll,
}) {
return NoDefaultCupertinoThemeData(
@ -539,6 +570,7 @@ class NoDefaultCupertinoThemeData {
textTheme: textTheme ?? this.textTheme,
barBackgroundColor: barBackgroundColor ?? this.barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
selectionHandleColor: selectionHandleColor ?? this.selectionHandleColor,
applyThemeToAll: applyThemeToAll ?? this.applyThemeToAll,
);
}
@ -581,6 +613,7 @@ class _CupertinoThemeDefaults {
this.primaryContrastingColor,
this.barBackgroundColor,
this.scaffoldBackgroundColor,
this.selectionHandleColor,
this.applyThemeToAll,
this.textThemeDefaults,
);
@ -590,6 +623,7 @@ class _CupertinoThemeDefaults {
final Color primaryContrastingColor;
final Color barBackgroundColor;
final Color scaffoldBackgroundColor;
final Color selectionHandleColor;
final bool applyThemeToAll;
final _CupertinoTextThemeDefaults textThemeDefaults;
@ -602,6 +636,7 @@ class _CupertinoThemeDefaults {
convertColor(primaryContrastingColor),
convertColor(barBackgroundColor),
convertColor(scaffoldBackgroundColor),
convertColor(selectionHandleColor),
applyThemeToAll,
resolveTextTheme ? textThemeDefaults.resolveFrom(context) : textThemeDefaults,
);

View File

@ -53,8 +53,8 @@ class TextSelectionThemeData with Diagnosticable {
///
/// On iOS [TextField] and [SelectableText] cannot access [selectionHandleColor].
/// To set the [selectionHandleColor] on iOS, you can change the
/// [CupertinoThemeData.primaryColor] by wrapping the subtree containing
/// your [TextField] or [SelectableText] with a [CupertinoTheme].
/// [CupertinoThemeData.selectionHandleColor] by wrapping the subtree
/// containing your [TextField] or [SelectableText] with a [CupertinoTheme].
final Color? selectionHandleColor;
/// Creates a copy of this object with the given fields replaced with the

View File

@ -2925,6 +2925,8 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
_cupertinoOverrideTheme.textTheme,
_cupertinoOverrideTheme.barBackgroundColor,
_cupertinoOverrideTheme.scaffoldBackgroundColor,
_cupertinoOverrideTheme.selectionHandleColor ??
_materialTheme.textSelectionTheme.selectionHandleColor,
_cupertinoOverrideTheme.applyThemeToAll,
);
@ -2964,6 +2966,7 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
Color? selectionHandleColor,
bool? applyThemeToAll,
}) {
return MaterialBasedCupertinoThemeData._(
@ -2975,6 +2978,7 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
selectionHandleColor: selectionHandleColor,
applyThemeToAll: applyThemeToAll,
),
);

View File

@ -674,10 +674,10 @@ void main() {
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(primaryColor: Colors.red),
theme: const CupertinoThemeData(selectionHandleColor: Colors.red),
home: Center(
child: CupertinoTheme(
data: const CupertinoThemeData(primaryColor: expectedSelectionHandleColor),
data: const CupertinoThemeData(selectionHandleColor: expectedSelectionHandleColor),
child: CupertinoTextField(controller: controller),
),
),

View File

@ -149,11 +149,45 @@ void main() {
});
group('cupertino handles', () {
testWidgets('draws custom handle correctly', (WidgetTester tester) async {
await tester.pumpWidget(
RepaintBoundary(
child: CupertinoTheme(
data: const CupertinoThemeData(selectionHandleColor: Color(0xFF9C27B0)),
child: Builder(
builder: (BuildContext context) {
return Container(
color: CupertinoColors.white,
height: 800,
width: 800,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 250),
child: FittedBox(
child: cupertinoTextSelectionControls.buildHandle(
context,
TextSelectionHandleType.right,
10.0,
),
),
),
);
},
),
),
),
);
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('text_selection.handle.custom.png'),
);
});
testWidgets('draws transparent handle correctly', (WidgetTester tester) async {
await tester.pumpWidget(
RepaintBoundary(
child: CupertinoTheme(
data: const CupertinoThemeData(primaryColor: Color(0x550000AA)),
data: const CupertinoThemeData(selectionHandleColor: Color(0x550000AA)),
child: Builder(
builder: (BuildContext context) {
return Container(

View File

@ -193,6 +193,7 @@ void main() {
'navActionTextStyle',
'pickerTextStyle',
'dateTimePickerTextStyle',
'selectionHandleColor',
}),
isTrue,
);
@ -272,6 +273,7 @@ void main() {
colorMatches(theme.primaryContrastingColor, CupertinoColors.white);
colorMatches(theme.barBackgroundColor, barBackgroundColor);
colorMatches(theme.scaffoldBackgroundColor, CupertinoColors.systemBackground);
colorMatches(theme.selectionHandleColor, CupertinoColors.systemBlue);
colorMatches(theme.textTheme.textStyle.color, CupertinoColors.label);
colorMatches(theme.textTheme.actionTextStyle.color, primaryColor);
colorMatches(theme.textTheme.tabLabelTextStyle.color, CupertinoColors.inactiveGray);