mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-10-19 16:17:03 +00:00
fix(mobile): prevent late mouse-down after floating button release (#16541)
Signed-off-by: zhenghe <zhenghe2009@gmail.com>
This commit is contained in:
@@ -372,6 +372,8 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
Offset _position = Offset.zero;
|
||||
bool _isInitialized = false;
|
||||
bool _isDown = false;
|
||||
Object? _activePress;
|
||||
bool _remoteButtonDown = false;
|
||||
Rect? _lastBlockedRect;
|
||||
|
||||
Orientation? _previousOrientation;
|
||||
@@ -398,6 +400,11 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_activePress = null;
|
||||
if (_remoteButtonDown) {
|
||||
_remoteButtonDown = false;
|
||||
_inputModel.tapUp(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}
|
||||
if (_lastBlockedRect != null) {
|
||||
_cursorModel.removeBlockedRect(_lastBlockedRect!);
|
||||
}
|
||||
@@ -569,6 +576,8 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
child: Listener(
|
||||
onPointerMove: _onBodyPointerMoveUpdate,
|
||||
onPointerDown: (event) async {
|
||||
final press = Object();
|
||||
_activePress = press;
|
||||
_isDragging = false;
|
||||
setState(() {
|
||||
_isDown = true;
|
||||
@@ -579,6 +588,8 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
isSpecialHoldDragActive = true;
|
||||
() async {
|
||||
await _cursorModel.syncCursorPosition();
|
||||
if (!mounted || !identical(_activePress, press)) return;
|
||||
_remoteButtonDown = true;
|
||||
await _inputModel
|
||||
.tapDown(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}();
|
||||
@@ -586,6 +597,8 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
});
|
||||
},
|
||||
onPointerUp: (event) {
|
||||
if (!mounted) return;
|
||||
_activePress = null;
|
||||
_cursorModel.blockEvents = false;
|
||||
setState(() {
|
||||
_isDown = false;
|
||||
@@ -604,8 +617,8 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
}));
|
||||
} else {
|
||||
// If it's not a quick tap, it could be a hold or drag.
|
||||
// If it was a hold, isSpecialHoldDragActive is true.
|
||||
if (isSpecialHoldDragActive) {
|
||||
if (_remoteButtonDown) {
|
||||
_remoteButtonDown = false;
|
||||
_inputModel
|
||||
.tapUp(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}
|
||||
@@ -617,13 +630,16 @@ class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
isSpecialHoldDragActive = false;
|
||||
},
|
||||
onPointerCancel: (event) {
|
||||
if (!mounted) return;
|
||||
_activePress = null;
|
||||
_cursorModel.blockEvents = false;
|
||||
setState(() {
|
||||
_isDown = false;
|
||||
});
|
||||
_tapDownTimer?.cancel();
|
||||
_tapDownTimer = null;
|
||||
if (isSpecialHoldDragActive) {
|
||||
if (_remoteButtonDown) {
|
||||
_remoteButtonDown = false;
|
||||
_inputModel.tapUp(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}
|
||||
isSpecialHoldDragActive = false;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
@TestOn('browser')
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:js' as js;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/remote_input.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/floating_mouse_widgets.dart';
|
||||
import 'package:flutter_hbb/models/input_model.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _Input extends Fake implements InputModel {
|
||||
final events = <(String, MouseButtons)>[];
|
||||
|
||||
@override
|
||||
Future<void> tapDown(MouseButtons button) async =>
|
||||
events.add(('down', button));
|
||||
|
||||
@override
|
||||
Future<void> tapUp(MouseButtons button) async => events.add(('up', button));
|
||||
}
|
||||
|
||||
class _FFI extends Fake implements FFI {}
|
||||
|
||||
class _Cursor extends CursorModel {
|
||||
_Cursor(FFI ffi) : super(WeakReference(ffi));
|
||||
final syncs = <Completer<void>>[];
|
||||
|
||||
@override
|
||||
Future<void> syncCursorPosition() {
|
||||
final sync = Completer<void>();
|
||||
syncs.add(sync);
|
||||
return sync.future;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
isSpecialHoldDragActive = false;
|
||||
js.context['isMobile'] = js.allowInterop(() => true);
|
||||
js.context['getByName'] =
|
||||
js.allowInterop((String name, [String? key]) => '');
|
||||
js.context['setByName'] = js.allowInterop((String name, dynamic value) {});
|
||||
});
|
||||
tearDown(() {
|
||||
isSpecialHoldDragActive = false;
|
||||
for (final name in ['isMobile', 'getByName', 'setByName']) {
|
||||
js.context.deleteProperty(name);
|
||||
}
|
||||
});
|
||||
|
||||
for (final end in ['release', 'cancel', 'dispose']) {
|
||||
testWidgets('pending hold cannot press after $end', (tester) async {
|
||||
final cancel = end == 'cancel';
|
||||
final ffi = _FFI();
|
||||
final input = _Input();
|
||||
final cursor = _Cursor(ffi);
|
||||
final button = cancel ? MouseButtons.right : MouseButtons.left;
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Stack(children: [
|
||||
FloatingLeftRightButton(
|
||||
isLeft: !cancel, inputModel: input, cursorModel: cursor),
|
||||
]),
|
||||
));
|
||||
await tester.pump();
|
||||
final target = find.byType(FloatingLeftRightButton);
|
||||
final first = await tester.startGesture(tester.getCenter(target));
|
||||
await tester.pump(const Duration(milliseconds: 201));
|
||||
expect(cursor.syncs, hasLength(1));
|
||||
if (end == 'dispose') {
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
cursor.syncs.single.complete();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
expect(input.events, isEmpty);
|
||||
return;
|
||||
}
|
||||
if (cancel) {
|
||||
await first.cancel();
|
||||
} else {
|
||||
await first.up();
|
||||
}
|
||||
// A new press must not make the old synchronization valid again.
|
||||
final second = await tester.startGesture(tester.getCenter(target));
|
||||
await tester.pump(const Duration(milliseconds: 201));
|
||||
cursor.syncs.first.complete();
|
||||
await tester.pump();
|
||||
expect(input.events, isEmpty);
|
||||
cursor.syncs.last.complete();
|
||||
await tester.pump();
|
||||
expect(input.events, [('down', button)]);
|
||||
await second.up();
|
||||
expect(input.events, [('down', button), ('up', button)]);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
expect(input.events, [('down', button), ('up', button)]);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user