Fix InputDecorators suffix and prefix widgets are tappable when hidden (#143308)

fixes [The InputDecoration's suffix and prefix widget can be tapped even if it does not appear](https://github.com/flutter/flutter/issues/139916)

This PR also updates two existing tests to pass the tests for this PR. These tests are trying to tap prefix and  suffix widgets when they're hidden. While the linked issue had visible prefix and suffix widgets https://github.com/flutter/flutter/issues/39376 for reproduction.

### Code sample

<details>
<summary>expand to view the code sample</summary> 

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

void main() {
  runApp(MainApp());
}

class MainApp extends StatelessWidget {
  final _messangerKey = GlobalKey<ScaffoldMessengerState>();

  MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      scaffoldMessengerKey: _messangerKey,
      home: Scaffold(
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(16.0),
          child: TextField(
            decoration: InputDecoration(
              labelText: 'Something',
              prefix: GestureDetector(
                onTap: () {
                  _messangerKey.currentState?.showSnackBar(
                      const SnackBar(content: Text('A tap has occurred')));
                },
                child: const Icon(Icons.search),
              ),
              suffix: GestureDetector(
                onTap: () {
                  _messangerKey.currentState?.showSnackBar(
                      const SnackBar(content: Text('A tap has occurred')));
                },
                child: const Icon(Icons.search),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
```

</details>

### Before
![ScreenRecording2024-02-12at18 40 34-ezgif com-video-to-gif-converter](https://github.com/flutter/flutter/assets/48603081/c101e0d6-ce5a-4b28-9626-28bcb83d2a5c)

### After
![ScreenRecording2024-02-12at18 40 10-ezgif com-video-to-gif-converter](https://github.com/flutter/flutter/assets/48603081/923b348e-8adf-4d64-9dc3-e75d30e3e2fb)
This commit is contained in:
Taha Tesser 2024-02-13 10:48:18 +02:00 committed by GitHub
parent e93a10d1fb
commit 1f8d110b8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 151 additions and 8 deletions

View file

@ -1728,14 +1728,17 @@ class _AffixText extends StatelessWidget {
Widget build(BuildContext context) {
return DefaultTextStyle.merge(
style: style,
child: AnimatedOpacity(
duration: _kTransitionDuration,
curve: _kTransitionCurve,
opacity: labelIsFloating ? 1.0 : 0.0,
child: Semantics(
sortKey: semanticsSortKey,
tagForChildren: semanticsTag,
child: child ?? (text == null ? null : Text(text!, style: style)),
child: IgnorePointer(
ignoring: !labelIsFloating,
child: AnimatedOpacity(
duration: _kTransitionDuration,
curve: _kTransitionCurve,
opacity: labelIsFloating ? 1.0 : 0.0,
child: Semantics(
sortKey: semanticsSortKey,
tagForChildren: semanticsTag,
child: child ?? (text == null ? null : Text(text!, style: style)),
),
),
),
);

View file

@ -307,6 +307,134 @@ void main() {
// 4 - bottom padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0));
}, variant: TargetPlatformVariant.desktop());
// This is a regression test for https://github.com/flutter/flutter/issues/139916.
testWidgets('Prefix ignores pointer when hidden', (WidgetTester tester) async {
bool tapped = false;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return TextField(
decoration: InputDecoration(
labelText: 'label',
prefix: GestureDetector(
onTap: () {
setState(() {
tapped = true;
});
},
child: const Icon(Icons.search),
),
),
);
}
),
),
),
);
expect(tapped, isFalse);
double prefixOpacity = tester.widget<AnimatedOpacity>(find.ancestor(
of: find.byType(Icon),
matching: find.byType(AnimatedOpacity),
)).opacity;
// Initially the prefix icon should be hidden.
expect(prefixOpacity, 0.0);
await tester.tap(find.byType(Icon), warnIfMissed: false); // Not expected to find the target.
await tester.pump();
// The suffix icon should ignore pointer events when hidden.
expect(tapped, isFalse);
// Tap the text field to show the prefix icon.
await tester.tap(find.byType(TextField));
await tester.pump();
prefixOpacity = tester.widget<AnimatedOpacity>(find.ancestor(
of: find.byType(Icon),
matching: find.byType(AnimatedOpacity),
)).opacity;
// The prefix icon should be visible.
expect(prefixOpacity, 1.0);
// Tap the prefix icon.
await tester.tap(find.byType(Icon));
await tester.pump();
// The prefix icon should be tapped.
expect(tapped, isTrue);
});
// This is a regression test for https://github.com/flutter/flutter/issues/139916.
testWidgets('Suffix ignores pointer when hidden', (WidgetTester tester) async {
bool tapped = false;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return TextField(
decoration: InputDecoration(
labelText: 'label',
suffix: GestureDetector(
onTap: () {
setState(() {
tapped = true;
});
},
child: const Icon(Icons.search),
),
),
);
}
),
),
),
);
expect(tapped, isFalse);
double suffixOpacity = tester.widget<AnimatedOpacity>(find.ancestor(
of: find.byType(Icon),
matching: find.byType(AnimatedOpacity),
)).opacity;
// Initially the suffix icon should be hidden.
expect(suffixOpacity, 0.0);
await tester.tap(find.byType(Icon), warnIfMissed: false); // Not expected to find the target.
await tester.pump();
// The suffix icon should ignore pointer events when hidden.
expect(tapped, isFalse);
// Tap the text field to show the suffix icon.
await tester.tap(find.byType(TextField));
await tester.pump();
suffixOpacity = tester.widget<AnimatedOpacity>(find.ancestor(
of: find.byType(Icon),
matching: find.byType(AnimatedOpacity),
)).opacity;
// The suffix icon should be visible.
expect(suffixOpacity, 1.0);
// Tap the suffix icon.
await tester.tap(find.byType(Icon));
await tester.pump();
// The suffix icon should be tapped.
expect(tapped, isTrue);
});
}
void runAllM2Tests() {

View file

@ -15570,10 +15570,12 @@ void main() {
int prefixTapCount = 0;
int suffixTapCount = 0;
final FocusNode focusNode = _focusNode();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: TextField(
focusNode: focusNode,
onTap: () { textFieldTapCount += 1; },
decoration: InputDecoration(
labelText: 'Label',
@ -15591,6 +15593,10 @@ void main() {
),
);
// Focus to show the prefix and suffix buttons.
focusNode.requestFocus();
await tester.pump();
TestGesture gesture =
await tester.startGesture(
tester.getRect(find.text('prefix')).center,
@ -15622,10 +15628,12 @@ void main() {
int prefixTapCount = 0;
int suffixTapCount = 0;
final FocusNode focusNode = _focusNode();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: TextField(
focusNode: focusNode,
onTap: () { textFieldTapCount += 1; },
decoration: InputDecoration(
labelText: 'Label',
@ -15643,6 +15651,10 @@ void main() {
),
);
// Focus to show the prefix and suffix buttons.
focusNode.requestFocus();
await tester.pump();
await tester.tap(find.text('prefix'));
expect(textFieldTapCount, 0);
expect(prefixTapCount, 1);