Skip to content

Commit 8914c91

Browse files
houssemeddinefadhli81victorsanniengine-flutter-autorollreidbaker9AZX
authored
feat: add onLongPressUp callback to InkWell widget (flutter#173221)
This PR introduces a new optional callback, onLongPressUp, to the InkWell widget. It allows developers to respond specifically to the moment when a long press gesture is released, which previously was not directly exposed. **Before** There was no way to distinguish between the long press being held and the moment it ended. **After** InkWell now accepts an onLongPressUp callback, which fires when the user lifts their finger after a long press gesture. This change enhances gesture handling granularity for widgets that require different behaviors for long press hold and release. **Related Issue** flutter#173390 **Tests** - Added a new test to ink_well_test.dart verifying that onLongPressUp is correctly triggered after a long press is released. **Checklist** - I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - I read the [Tree Hygiene] wiki page. - I followed the [Flutter Style Guide]. - I signed the [CLA]. - I updated/added relevant documentation (/// doc comments). - I added new tests to check the change I am making. - All existing and new tests are passing. --------- Co-authored-by: Victor Sanni <[email protected]> Co-authored-by: engine-flutter-autoroll <[email protected]> Co-authored-by: Reid Baker <[email protected]> Co-authored-by: Jon Ihlas <[email protected]> Co-authored-by: Matthew Kosarek <[email protected]> Co-authored-by: gaaclarke <[email protected]> Co-authored-by: Micael Cid <[email protected]> Co-authored-by: Alexander Aprelev <[email protected]> Co-authored-by: Tong Mu <[email protected]>
1 parent d8186f7 commit 8914c91

File tree

2 files changed

+82
-6
lines changed

2 files changed

+82
-6
lines changed

packages/flutter/lib/src/material/ink_well.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ class InkResponse extends StatelessWidget {
311311
this.onTapCancel,
312312
this.onDoubleTap,
313313
this.onLongPress,
314+
this.onLongPressUp,
314315
this.onSecondaryTap,
315316
this.onSecondaryTapUp,
316317
this.onSecondaryTapDown,
@@ -364,6 +365,19 @@ class InkResponse extends StatelessWidget {
364365
/// Called when the user long-presses on this part of the material.
365366
final GestureLongPressCallback? onLongPress;
366367

368+
/// Called when the user lifts their finger after a long press on the button.
369+
///
370+
/// This callback is triggered at the end of a long press gesture, specifically
371+
/// after the user holds a long press and then releases it. It does not include
372+
/// position details.
373+
///
374+
/// Common use cases include performing an action only after the long press completes,
375+
/// such as displaying a context menu or confirming a held gesture.
376+
///
377+
/// See also:
378+
/// * [onLongPress], which is triggered when the long press gesture is first recognized.
379+
final GestureLongPressUpCallback? onLongPressUp;
380+
367381
/// Called when the user taps this part of the material with a secondary button.
368382
///
369383
/// See also:
@@ -404,7 +418,7 @@ class InkResponse extends StatelessWidget {
404418
/// become highlighted and false if this part of the material has stopped
405419
/// being highlighted.
406420
///
407-
/// If all of [onTap], [onDoubleTap], and [onLongPress] become null while a
421+
/// If all of [onTap], [onDoubleTap], [onLongPress], and [onLongPressUp] become null while a
408422
/// gesture is ongoing, then [onTapCancel] will be fired and
409423
/// [onHighlightChanged] will be fired with the value false _during the
410424
/// build_. This means, for instance, that in that scenario [State.setState]
@@ -659,6 +673,7 @@ class InkResponse extends StatelessWidget {
659673
onTapCancel: onTapCancel,
660674
onDoubleTap: onDoubleTap,
661675
onLongPress: onLongPress,
676+
onLongPressUp: onLongPressUp,
662677
onSecondaryTap: onSecondaryTap,
663678
onSecondaryTapUp: onSecondaryTapUp,
664679
onSecondaryTapDown: onSecondaryTapDown,
@@ -716,6 +731,7 @@ class _InkResponseStateWidget extends StatefulWidget {
716731
this.onTapCancel,
717732
this.onDoubleTap,
718733
this.onLongPress,
734+
this.onLongPressUp,
719735
this.onSecondaryTap,
720736
this.onSecondaryTapUp,
721737
this.onSecondaryTapDown,
@@ -754,6 +770,7 @@ class _InkResponseStateWidget extends StatefulWidget {
754770
final GestureTapCallback? onTapCancel;
755771
final GestureTapCallback? onDoubleTap;
756772
final GestureLongPressCallback? onLongPress;
773+
final GestureLongPressUpCallback? onLongPressUp;
757774
final GestureTapCallback? onSecondaryTap;
758775
final GestureTapUpCallback? onSecondaryTapUp;
759776
final GestureTapDownCallback? onSecondaryTapDown;
@@ -794,6 +811,7 @@ class _InkResponseStateWidget extends StatefulWidget {
794811
if (onTap != null) 'tap',
795812
if (onDoubleTap != null) 'double tap',
796813
if (onLongPress != null) 'long press',
814+
if (onLongPressUp != null) 'long press up',
797815
if (onTapDown != null) 'tap down',
798816
if (onTapUp != null) 'tap up',
799817
if (onTapCancel != null) 'tap cancel',
@@ -1230,6 +1248,12 @@ class _InkResponseState extends State<_InkResponseStateWidget>
12301248
}
12311249
}
12321250

1251+
void handleLongPressUp() {
1252+
_currentSplash?.confirm();
1253+
_currentSplash = null;
1254+
widget.onLongPressUp?.call();
1255+
}
1256+
12331257
void handleSecondaryTap() {
12341258
_currentSplash?.confirm();
12351259
_currentSplash = null;
@@ -1271,6 +1295,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
12711295
return widget.onTap != null ||
12721296
widget.onDoubleTap != null ||
12731297
widget.onLongPress != null ||
1298+
widget.onLongPressUp != null ||
12741299
widget.onTapUp != null ||
12751300
widget.onTapDown != null;
12761301
}
@@ -1392,6 +1417,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
13921417
onTapCancel: _primaryEnabled ? handleTapCancel : null,
13931418
onDoubleTap: widget.onDoubleTap != null ? handleDoubleTap : null,
13941419
onLongPress: widget.onLongPress != null ? handleLongPress : null,
1420+
onLongPressUp: widget.onLongPressUp != null ? handleLongPressUp : null,
13951421
onSecondaryTapDown: _secondaryEnabled ? handleSecondaryTapDown : null,
13961422
onSecondaryTapUp: _secondaryEnabled ? handleSecondaryTapUp : null,
13971423
onSecondaryTap: _secondaryEnabled ? handleSecondaryTap : null,
@@ -1497,6 +1523,7 @@ class InkWell extends InkResponse {
14971523
super.onTap,
14981524
super.onDoubleTap,
14991525
super.onLongPress,
1526+
super.onLongPressUp,
15001527
super.onTapDown,
15011528
super.onTapUp,
15021529
super.onTapCancel,

packages/flutter/test/material/ink_well_test.dart

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
77
import 'package:flutter/rendering.dart';
88
import 'package:flutter/src/services/keyboard_key.g.dart';
99
import 'package:flutter_test/flutter_test.dart';
10+
1011
import '../widgets/feedback_tester.dart';
1112
import '../widgets/semantics_tester.dart';
1213

@@ -35,6 +36,9 @@ void main() {
3536
onLongPress: () {
3637
log.add('long-press');
3738
},
39+
onLongPressUp: () {
40+
log.add('long-press-up');
41+
},
3842
onTapDown: (TapDownDetails details) {
3943
log.add('tap-down');
4044
},
@@ -68,7 +72,7 @@ void main() {
6872

6973
await tester.longPress(find.byType(InkWell), pointer: 4);
7074

71-
expect(log, equals(<String>['tap-down', 'tap-cancel', 'long-press']));
75+
expect(log, equals(<String>['tap-down', 'tap-cancel', 'long-press', 'long-press-up']));
7276

7377
log.clear();
7478
TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
@@ -142,6 +146,31 @@ void main() {
142146
expect(log, equals(<String>['tap']));
143147
});
144148

149+
testWidgets('InkWell onLongPressUp callback is triggered', (WidgetTester tester) async {
150+
bool wasCalled = false;
151+
152+
await tester.pumpWidget(
153+
MaterialApp(
154+
home: Material(
155+
child: InkWell(
156+
onLongPress: () {},
157+
onLongPressUp: () {
158+
wasCalled = true;
159+
},
160+
child: const SizedBox(width: 100, height: 100),
161+
),
162+
),
163+
),
164+
);
165+
166+
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(InkWell)));
167+
await tester.pump(const Duration(seconds: 1));
168+
await gesture.up();
169+
await tester.pumpAndSettle();
170+
171+
expect(wasCalled, isTrue);
172+
});
173+
145174
testWidgets('long-press and tap on disabled should not throw', (WidgetTester tester) async {
146175
await tester.pumpWidget(
147176
const Material(
@@ -173,6 +202,7 @@ void main() {
173202
highlightColor: const Color(0xf00fffff),
174203
onTap: () {},
175204
onLongPress: () {},
205+
onLongPressUp: () {},
176206
onHover: (bool hover) {},
177207
),
178208
),
@@ -220,6 +250,7 @@ void main() {
220250
}),
221251
onTap: () {},
222252
onLongPress: () {},
253+
onLongPressUp: () {},
223254
onHover: (bool hover) {},
224255
),
225256
),
@@ -260,6 +291,7 @@ void main() {
260291
highlightColor: const Color(0xf00fffff),
261292
onTap: () {},
262293
onLongPress: () {},
294+
onLongPressUp: () {},
263295
onHover: (bool hover) {},
264296
),
265297
),
@@ -312,6 +344,7 @@ void main() {
312344
highlightColor: const Color(0xf00fffff),
313345
onTap: () {},
314346
onLongPress: () {},
347+
onLongPressUp: () {},
315348
onHover: (bool hover) {},
316349
),
317350
),
@@ -495,6 +528,7 @@ void main() {
495528
highlightColor: const Color(0xf00fffff),
496529
onTap: () {},
497530
onLongPress: () {},
531+
onLongPressUp: () {},
498532
onHover: (bool hover) {},
499533
),
500534
),
@@ -553,6 +587,7 @@ void main() {
553587
}),
554588
onTap: () {},
555589
onLongPress: () {},
590+
onLongPressUp: () {},
556591
onHover: (bool hover) {},
557592
),
558593
),
@@ -1110,6 +1145,7 @@ void main() {
11101145
highlightColor: const Color(0xf00fffff),
11111146
onTap: () {},
11121147
onLongPress: () {},
1148+
onLongPressUp: () {},
11131149
onHover: (bool hover) {},
11141150
),
11151151
),
@@ -1263,7 +1299,7 @@ void main() {
12631299
child: Directionality(
12641300
textDirection: TextDirection.ltr,
12651301
child: Center(
1266-
child: InkWell(onTap: () {}, onLongPress: () {}),
1302+
child: InkWell(onTap: () {}, onLongPress: () {}, onLongPressUp: () {}),
12671303
),
12681304
),
12691305
),
@@ -1290,7 +1326,12 @@ void main() {
12901326
child: Directionality(
12911327
textDirection: TextDirection.ltr,
12921328
child: Center(
1293-
child: InkWell(onTap: () {}, onLongPress: () {}, enableFeedback: false),
1329+
child: InkWell(
1330+
onTap: () {},
1331+
onLongPress: () {},
1332+
onLongPressUp: () {},
1333+
enableFeedback: false,
1334+
),
12941335
),
12951336
),
12961337
),
@@ -1411,6 +1452,7 @@ void main() {
14111452
autofocus: true,
14121453
onTap: () {},
14131454
onLongPress: () {},
1455+
onLongPressUp: () {},
14141456
onHover: (bool hover) {},
14151457
focusNode: focusNode,
14161458
child: Container(key: childKey),
@@ -1452,6 +1494,7 @@ void main() {
14521494
autofocus: true,
14531495
onTap: () {},
14541496
onLongPress: () {},
1497+
onLongPressUp: () {},
14551498
onHover: (bool hover) {},
14561499
focusNode: focusNode,
14571500
child: Container(key: childKey),
@@ -2175,7 +2218,7 @@ void main() {
21752218
textDirection: TextDirection.ltr,
21762219
child: Material(
21772220
child: Center(
2178-
child: InkWell(onLongPress: () {}, child: const Text('Foo')),
2221+
child: InkWell(onLongPress: () {}, onLongPressUp: () {}, child: const Text('Foo')),
21792222
),
21802223
),
21812224
),
@@ -2198,7 +2241,12 @@ void main() {
21982241
textDirection: TextDirection.ltr,
21992242
child: Material(
22002243
child: Center(
2201-
child: InkWell(onLongPress: () {}, onTap: () {}, child: const Text('Foo')),
2244+
child: InkWell(
2245+
onLongPress: () {},
2246+
onLongPressUp: () {},
2247+
onTap: () {},
2248+
child: const Text('Foo'),
2249+
),
22022250
),
22032251
),
22042252
),
@@ -2382,6 +2430,7 @@ void main() {
23822430
}),
23832431
onTap: () {},
23842432
onLongPress: () {},
2433+
onLongPressUp: () {},
23852434
onHover: (bool hover) {},
23862435
),
23872436
),

0 commit comments

Comments
 (0)