mirror of
https://github.com/block/buzz.git
synced 2026-11-01 23:11:58 +00:00
Require a two-finger double tap for connection options
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import 'package:flutter/foundation.dart';
|
||||
import '../../shared/widgets/frosted_app_bar.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/semantics.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:buzz/shared/theme/buzz_icons.dart';
|
||||
@@ -36,6 +37,7 @@ part 'pairing_page/onboarding_background.dart';
|
||||
part 'pairing_page/onboarding_colors.dart';
|
||||
part 'pairing_page/onboarding_glass_button.dart';
|
||||
part 'pairing_page/pairing_welcome_view.dart';
|
||||
part 'pairing_page/two_finger_double_tap.dart';
|
||||
part 'pairing_page/community_link_form.dart';
|
||||
part 'pairing_page/onboarding_connection_view.dart';
|
||||
part 'pairing_page/sas_verification_view.dart';
|
||||
|
||||
@@ -345,10 +345,7 @@ class _PairingWelcomeView extends HookWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
// Keep double-tap recognition off the action buttons so
|
||||
// their single taps do not wait for the double-tap timeout.
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: _TwoFingerDoubleTapSurface(
|
||||
onDoubleTap: canRevealOptions ? revealOptions : null,
|
||||
child: Center(
|
||||
child: Column(
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
part of '../pairing_page.dart';
|
||||
|
||||
class _TwoFingerDoubleTapSurface extends HookWidget {
|
||||
const _TwoFingerDoubleTapSurface({required this.child, this.onDoubleTap});
|
||||
|
||||
final Widget child;
|
||||
final VoidCallback? onDoubleTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final taps = useMemoized(_TwoFingerTapTracker.new, [onDoubleTap != null]);
|
||||
return Listener(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPointerDown: onDoubleTap == null ? null : taps.down,
|
||||
onPointerMove: onDoubleTap == null ? null : taps.move,
|
||||
onPointerCancel: onDoubleTap == null ? null : (_) => taps.cancel(),
|
||||
onPointerUp: onDoubleTap == null
|
||||
? null
|
||||
: (event) => taps.up(event, onDoubleTap!),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Observe two short, stationary two-finger chords without competing with taps
|
||||
// or scrolling in Flutter's gesture arena.
|
||||
class _TwoFingerTapTracker {
|
||||
final _starts = <int, Offset>{};
|
||||
final _down = <int>{};
|
||||
Duration _started = Duration.zero;
|
||||
Duration? _previousEnd;
|
||||
Offset? _previousCenter;
|
||||
bool _valid = true;
|
||||
|
||||
void down(PointerDownEvent event) {
|
||||
if (_down.isEmpty) {
|
||||
_starts.clear();
|
||||
_started = event.timeStamp;
|
||||
_valid = true;
|
||||
}
|
||||
_starts[event.pointer] = event.position;
|
||||
_down.add(event.pointer);
|
||||
if (_starts.length > 2) _valid = false;
|
||||
}
|
||||
|
||||
void move(PointerMoveEvent event) {
|
||||
final start = _starts[event.pointer];
|
||||
if (start != null && (event.position - start).distance > kTouchSlop) {
|
||||
_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
_down.clear();
|
||||
_starts.clear();
|
||||
_previousEnd = null;
|
||||
_previousCenter = null;
|
||||
_valid = false;
|
||||
}
|
||||
|
||||
void up(PointerUpEvent event, VoidCallback onDoubleTap) {
|
||||
if (!_down.remove(event.pointer) || _down.isNotEmpty) return;
|
||||
if (!_valid ||
|
||||
_starts.length != 2 ||
|
||||
event.timeStamp - _started > kDoubleTapTimeout) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
final center = (_starts.values.first + _starts.values.last) / 2;
|
||||
final gap = _previousEnd == null ? null : _started - _previousEnd!;
|
||||
final isDouble =
|
||||
gap != null &&
|
||||
gap >= kDoubleTapMinTime &&
|
||||
gap <= kDoubleTapTimeout &&
|
||||
(center - _previousCenter!).distance <= kDoubleTapSlop;
|
||||
_previousEnd = event.timeStamp;
|
||||
_previousCenter = center;
|
||||
if (isDouble) {
|
||||
cancel();
|
||||
onDoubleTap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,7 +298,27 @@ void main() {
|
||||
}
|
||||
|
||||
Future<void> revealOptions(WidgetTester tester) async {
|
||||
// Ordinary double taps must not expose the hidden connection options.
|
||||
await tester.tapAt(const Offset(24, 120));
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await tester.tapAt(const Offset(24, 120));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Use a pairing code').hitTestable(), findsNothing);
|
||||
|
||||
for (var tap = 0; tap < 2; tap++) {
|
||||
final first = await tester.createGesture(pointer: 11);
|
||||
final second = await tester.createGesture(pointer: 12);
|
||||
await first.down(
|
||||
const Offset(24, 120),
|
||||
timeStamp: Duration(milliseconds: tap * 70),
|
||||
);
|
||||
await second.down(
|
||||
const Offset(70, 120),
|
||||
timeStamp: Duration(milliseconds: tap * 70),
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
await first.up(timeStamp: Duration(milliseconds: tap * 70 + 20));
|
||||
await second.up(timeStamp: Duration(milliseconds: tap * 70 + 20));
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user