add custom gestures
This commit is contained in:
parent
b60e276c98
commit
f5f496f1cf
770
lib/gestures.dart
Normal file
770
lib/gestures.dart
Normal file
@ -0,0 +1,770 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
enum CustomTouchGestureState {
|
||||||
|
none,
|
||||||
|
oneFingerPan,
|
||||||
|
twoFingerScale,
|
||||||
|
twoFingerVerticalDrag,
|
||||||
|
twoFingerHorizontalDrag
|
||||||
|
}
|
||||||
|
|
||||||
|
const kScaleSlop = kPrecisePointerPanSlop / 10;
|
||||||
|
|
||||||
|
class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
|
CustomTouchGestureRecognizer({
|
||||||
|
Object debugOwner,
|
||||||
|
Set<PointerDeviceKind> supportedDevices,
|
||||||
|
}) : super(
|
||||||
|
debugOwner: debugOwner,
|
||||||
|
supportedDevices: supportedDevices,
|
||||||
|
) {
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// oneFingerPan
|
||||||
|
GestureDragStartCallback onOneFingerPanStart;
|
||||||
|
GestureDragUpdateCallback onOneFingerPanUpdate;
|
||||||
|
GestureDragEndCallback onOneFingerPanEnd;
|
||||||
|
|
||||||
|
// twoFingerScale
|
||||||
|
GestureScaleStartCallback onTwoFingerScaleStart;
|
||||||
|
GestureScaleUpdateCallback onTwoFingerScaleUpdate;
|
||||||
|
GestureScaleEndCallback onTwoFingerScaleEnd;
|
||||||
|
|
||||||
|
// twoFingerVerticalDrag
|
||||||
|
GestureDragStartCallback onTwoFingerVerticalDragStart;
|
||||||
|
GestureDragUpdateCallback onTwoFingerVerticalDragUpdate;
|
||||||
|
GestureDragEndCallback onTwoFingerVerticalDragEnd;
|
||||||
|
|
||||||
|
// twoFingerHorizontalDrag
|
||||||
|
GestureDragStartCallback onTwoFingerHorizontalDragStart;
|
||||||
|
GestureDragUpdateCallback onTwoFingerHorizontalDragUpdate;
|
||||||
|
GestureDragEndCallback onTwoFingerHorizontalDragEnd;
|
||||||
|
|
||||||
|
void _init() {
|
||||||
|
debugPrint("CustomTouchGestureRecognizer init");
|
||||||
|
onStart = (d) {
|
||||||
|
if (d.pointerCount == 1) {
|
||||||
|
_currentState = CustomTouchGestureState.oneFingerPan;
|
||||||
|
debugPrint("start pan");
|
||||||
|
} else if (d.pointerCount == 2) {
|
||||||
|
_currentState = CustomTouchGestureState.none;
|
||||||
|
startWatchTimer();
|
||||||
|
} else {
|
||||||
|
_currentState = CustomTouchGestureState.none;
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onUpdate = (d) {
|
||||||
|
if (_isWatch) {
|
||||||
|
_updateCompute(d);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_currentState != CustomTouchGestureState.none) {
|
||||||
|
switch (_currentState) {
|
||||||
|
case CustomTouchGestureState.oneFingerPan:
|
||||||
|
if (onOneFingerPanUpdate != null) {
|
||||||
|
onOneFingerPanUpdate(_getDragUpdateDetails(d));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CustomTouchGestureState.twoFingerScale:
|
||||||
|
if (onTwoFingerScaleUpdate != null) {
|
||||||
|
onTwoFingerScaleUpdate(d);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CustomTouchGestureState.twoFingerHorizontalDrag:
|
||||||
|
if (onTwoFingerHorizontalDragUpdate != null) {
|
||||||
|
onTwoFingerHorizontalDragUpdate(_getDragUpdateDetails(d));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CustomTouchGestureState.twoFingerVerticalDrag:
|
||||||
|
if (onTwoFingerVerticalDragUpdate != null) {
|
||||||
|
onTwoFingerVerticalDragUpdate(_getDragUpdateDetails(d));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onEnd = (d) {
|
||||||
|
debugPrint("ScaleGestureRecognizer onEnd");
|
||||||
|
// end
|
||||||
|
switch (_currentState) {
|
||||||
|
case CustomTouchGestureState.oneFingerPan:
|
||||||
|
debugPrint("TwoFingerState.pan onEnd");
|
||||||
|
if (onOneFingerPanEnd != null) {
|
||||||
|
onOneFingerPanEnd(_getDragEndDetails(d));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CustomTouchGestureState.twoFingerScale:
|
||||||
|
debugPrint("TwoFingerState.scale onEnd");
|
||||||
|
if (onTwoFingerScaleEnd != null) {
|
||||||
|
onTwoFingerScaleEnd(d);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CustomTouchGestureState.twoFingerHorizontalDrag:
|
||||||
|
debugPrint("TwoFingerState.horizontal onEnd");
|
||||||
|
if (onTwoFingerHorizontalDragEnd != null) {
|
||||||
|
onTwoFingerHorizontalDragEnd(_getDragEndDetails(d));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CustomTouchGestureState.twoFingerVerticalDrag:
|
||||||
|
debugPrint("TwoFingerState.vertical onEnd");
|
||||||
|
if (onTwoFingerVerticalDragEnd != null) {
|
||||||
|
onTwoFingerVerticalDragEnd(_getDragEndDetails(d));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_currentState = CustomTouchGestureState.none;
|
||||||
|
_reset();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var _currentState = CustomTouchGestureState.none;
|
||||||
|
var _isWatch = false;
|
||||||
|
|
||||||
|
Timer _timer;
|
||||||
|
double _sumScale = 0;
|
||||||
|
double _sumVertical = 0;
|
||||||
|
double _sumHorizontal = 0;
|
||||||
|
|
||||||
|
void _clearSum() {
|
||||||
|
_sumScale = 0;
|
||||||
|
_sumVertical = 0;
|
||||||
|
_sumHorizontal = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
_isWatch = false;
|
||||||
|
_clearSum();
|
||||||
|
if (_timer != null) _timer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateCompute(ScaleUpdateDetails d) {
|
||||||
|
_sumScale += d.scale - 1;
|
||||||
|
_sumHorizontal += d.focalPointDelta.dx;
|
||||||
|
_sumVertical += d.focalPointDelta.dy;
|
||||||
|
// start
|
||||||
|
if (_sumScale.abs() > kScaleSlop) {
|
||||||
|
debugPrint("start Scale");
|
||||||
|
_currentState = CustomTouchGestureState.twoFingerScale;
|
||||||
|
if (onOneFingerPanStart != null) {
|
||||||
|
onOneFingerPanStart(_getDragStartDetails(d));
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
} else if (_sumHorizontal.abs() > kPrecisePointerPanSlop) {
|
||||||
|
debugPrint("start Horizontal");
|
||||||
|
_currentState = CustomTouchGestureState.twoFingerHorizontalDrag;
|
||||||
|
if (onTwoFingerHorizontalDragUpdate != null) {
|
||||||
|
onTwoFingerHorizontalDragUpdate(_getDragUpdateDetails(d));
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
} else if (_sumVertical.abs() > kPrecisePointerPanSlop) {
|
||||||
|
debugPrint("start Vertical");
|
||||||
|
if (onTwoFingerVerticalDragStart != null) {
|
||||||
|
_getDragStartDetails(d);
|
||||||
|
}
|
||||||
|
_currentState = CustomTouchGestureState.twoFingerVerticalDrag;
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startWatchTimer() {
|
||||||
|
debugPrint("startWatchTimer");
|
||||||
|
_isWatch = true;
|
||||||
|
_clearSum();
|
||||||
|
if (_timer != null) _timer.cancel();
|
||||||
|
_timer = Timer(const Duration(milliseconds: 200), _reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
DragStartDetails _getDragStartDetails(ScaleUpdateDetails d) =>
|
||||||
|
DragStartDetails(
|
||||||
|
globalPosition: d.focalPoint,
|
||||||
|
localPosition: d.localFocalPoint,
|
||||||
|
);
|
||||||
|
|
||||||
|
DragUpdateDetails _getDragUpdateDetails(ScaleUpdateDetails d) =>
|
||||||
|
DragUpdateDetails(
|
||||||
|
globalPosition: d.focalPoint,
|
||||||
|
localPosition: d.localFocalPoint,
|
||||||
|
delta: d.focalPointDelta);
|
||||||
|
|
||||||
|
DragEndDetails _getDragEndDetails(ScaleEndDetails d) =>
|
||||||
|
DragEndDetails(velocity: d.velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HoldTapMoveGestureRecognizer extends GestureRecognizer {
|
||||||
|
HoldTapMoveGestureRecognizer({
|
||||||
|
Object debugOwner,
|
||||||
|
Set<PointerDeviceKind> supportedDevices,
|
||||||
|
}) : super(
|
||||||
|
debugOwner: debugOwner,
|
||||||
|
supportedDevices: supportedDevices,
|
||||||
|
);
|
||||||
|
|
||||||
|
GestureDragStartCallback onHoldDragStart;
|
||||||
|
GestureDragUpdateCallback onHoldDragUpdate;
|
||||||
|
GestureDragDownCallback onHoldDragDown;
|
||||||
|
GestureDragCancelCallback onHoldDragCancel;
|
||||||
|
|
||||||
|
bool _isStart = false;
|
||||||
|
|
||||||
|
Timer _firstTapUpTimer; // 第一次点击后的计时 超时未等到第二次操作则reject
|
||||||
|
Timer _secondTapDownTimer; // 第二次点击后的计时 期间内有其他的操作则reject 超时则判定成功 drag update
|
||||||
|
_TapTracker _firstTap;
|
||||||
|
_TapTracker _secondTap;
|
||||||
|
|
||||||
|
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isPointerAllowed(PointerDownEvent event) {
|
||||||
|
if (_firstTap == null) {
|
||||||
|
switch (event.buttons) {
|
||||||
|
case kPrimaryButton:
|
||||||
|
if (onHoldDragStart == null &&
|
||||||
|
onHoldDragUpdate == null &&
|
||||||
|
onHoldDragCancel == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.isPointerAllowed(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
|
// 检测按下事件
|
||||||
|
if (_firstTap != null) {
|
||||||
|
if (!_firstTap.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
|
||||||
|
// Ignore out-of-bounds second taps.
|
||||||
|
return;
|
||||||
|
} else if (!_firstTap.hasElapsedMinTime() ||
|
||||||
|
!_firstTap.hasSameButton(event)) {
|
||||||
|
// Restart when the second tap is too close to the first (touch screens
|
||||||
|
// often detect touches intermittently), or when buttons mismatch.
|
||||||
|
_reset();
|
||||||
|
return _trackTap(event);
|
||||||
|
} else if (onHoldDragDown != null) {
|
||||||
|
invokeCallback<void>(
|
||||||
|
'onHoldDragDown',
|
||||||
|
() => onHoldDragDown(DragDownDetails(
|
||||||
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_trackTap(event); // 捕捉第一次tap
|
||||||
|
}
|
||||||
|
|
||||||
|
void _trackTap(PointerDownEvent event) {
|
||||||
|
_stopFirstTapUpTimer();
|
||||||
|
_stopSecondTapDownTimer();
|
||||||
|
final _TapTracker tracker = _TapTracker(
|
||||||
|
event: event,
|
||||||
|
entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
|
||||||
|
doubleTapMinTime: kDoubleTapMinTime,
|
||||||
|
gestureSettings: gestureSettings,
|
||||||
|
);
|
||||||
|
_trackers[event.pointer] = tracker;
|
||||||
|
tracker.startTrackingPointer(_handleEvent, event.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际的逻辑应该是第二次down后一段时间没有抬起则表示start 刚好是双击取反
|
||||||
|
void _handleEvent(PointerEvent event) {
|
||||||
|
final _TapTracker tracker = _trackers[event.pointer];
|
||||||
|
if (event is PointerUpEvent) {
|
||||||
|
if (_firstTap == null && _secondTap == null) {
|
||||||
|
_registerFirstTap(tracker);
|
||||||
|
} else {
|
||||||
|
// 检测到其他的抬起事件则取消
|
||||||
|
_reject(tracker);
|
||||||
|
}
|
||||||
|
} else if (event is PointerDownEvent) {
|
||||||
|
if (_firstTap != null && _secondTap == null) {
|
||||||
|
_registerSecondTap(tracker);
|
||||||
|
}
|
||||||
|
} else if (event is PointerMoveEvent) {
|
||||||
|
// 检测到first tap move 则取消,检测到second tap move且已经通过竞技场则update
|
||||||
|
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
|
||||||
|
if (_firstTap != null && _firstTap.pointer == event.pointer) {
|
||||||
|
// first tap move
|
||||||
|
_reject(tracker);
|
||||||
|
} else if (_secondTap != null && _secondTap.pointer == event.pointer) {
|
||||||
|
// debugPrint("_secondTap move");
|
||||||
|
// second tap move
|
||||||
|
if (!_isStart) {
|
||||||
|
_resolve();
|
||||||
|
}
|
||||||
|
if (onHoldDragUpdate != null)
|
||||||
|
onHoldDragUpdate(DragUpdateDetails(
|
||||||
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
|
delta: event.delta));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (event is PointerCancelEvent) {
|
||||||
|
_reject(tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void acceptGesture(int pointer) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void rejectGesture(int pointer) {
|
||||||
|
_TapTracker tracker = _trackers[pointer];
|
||||||
|
// If tracker isn't in the list, check if this is the first tap tracker
|
||||||
|
if (tracker == null && _firstTap != null && _firstTap.pointer == pointer) {
|
||||||
|
tracker = _firstTap;
|
||||||
|
}
|
||||||
|
// If tracker is still null, we rejected ourselves already
|
||||||
|
if (tracker != null) {
|
||||||
|
_reject(tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resolve() {
|
||||||
|
_stopSecondTapDownTimer();
|
||||||
|
_firstTap?.entry?.resolve(GestureDisposition.accepted);
|
||||||
|
_secondTap?.entry?.resolve(GestureDisposition.accepted);
|
||||||
|
_isStart = true;
|
||||||
|
// TODO start details
|
||||||
|
if (onHoldDragStart != null) onHoldDragStart(DragStartDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reject(_TapTracker tracker) {
|
||||||
|
_checkCancel();
|
||||||
|
|
||||||
|
_isStart = false;
|
||||||
|
_trackers.remove(tracker.pointer);
|
||||||
|
tracker.entry.resolve(GestureDisposition.rejected);
|
||||||
|
_freezeTracker(tracker);
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_reset();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
_isStart = false;
|
||||||
|
// debugPrint("reset");
|
||||||
|
_stopFirstTapUpTimer();
|
||||||
|
_stopSecondTapDownTimer();
|
||||||
|
if (_firstTap != null) {
|
||||||
|
if (_trackers.isNotEmpty) {
|
||||||
|
_checkCancel();
|
||||||
|
}
|
||||||
|
// Note, order is important below in order for the resolve -> reject logic
|
||||||
|
// to work properly.
|
||||||
|
final _TapTracker tracker = _firstTap;
|
||||||
|
_firstTap = null;
|
||||||
|
_reject(tracker);
|
||||||
|
GestureBinding.instance.gestureArena.release(tracker.pointer);
|
||||||
|
|
||||||
|
if (_secondTap != null) {
|
||||||
|
final _TapTracker tracker = _secondTap;
|
||||||
|
_secondTap = null;
|
||||||
|
_reject(tracker);
|
||||||
|
GestureBinding.instance.gestureArena.release(tracker.pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO 正确的释放资源
|
||||||
|
_firstTap = null;
|
||||||
|
_secondTap = null;
|
||||||
|
_clearTrackers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerFirstTap(_TapTracker tracker) {
|
||||||
|
_startFirstTapUpTimer();
|
||||||
|
GestureBinding.instance.gestureArena.hold(tracker.pointer);
|
||||||
|
// Note, order is important below in order for the clear -> reject logic to
|
||||||
|
// work properly.
|
||||||
|
_freezeTracker(tracker);
|
||||||
|
_trackers.remove(tracker.pointer);
|
||||||
|
_firstTap = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerSecondTap(_TapTracker tracker) {
|
||||||
|
// 清除first tap的状态
|
||||||
|
if (_firstTap != null) {
|
||||||
|
_stopFirstTapUpTimer();
|
||||||
|
_freezeTracker(_firstTap);
|
||||||
|
_firstTap = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startSecondTapDownTimer();
|
||||||
|
GestureBinding.instance.gestureArena.hold(tracker.pointer);
|
||||||
|
|
||||||
|
_secondTap = tracker;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearTrackers() {
|
||||||
|
_trackers.values.toList().forEach(_reject);
|
||||||
|
assert(_trackers.isEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _freezeTracker(_TapTracker tracker) {
|
||||||
|
tracker.stopTrackingPointer(_handleEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startFirstTapUpTimer() {
|
||||||
|
_firstTapUpTimer ??= Timer(kDoubleTapTimeout, _reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startSecondTapDownTimer() {
|
||||||
|
_secondTapDownTimer ??= Timer(kDoubleTapTimeout, _resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopFirstTapUpTimer() {
|
||||||
|
if (_firstTapUpTimer != null) {
|
||||||
|
_firstTapUpTimer.cancel();
|
||||||
|
_firstTapUpTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopSecondTapDownTimer() {
|
||||||
|
if (_secondTapDownTimer != null) {
|
||||||
|
_secondTapDownTimer.cancel();
|
||||||
|
_secondTapDownTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkCancel() {
|
||||||
|
if (onHoldDragCancel != null) {
|
||||||
|
invokeCallback<void>('onHoldDragCancel', onHoldDragCancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugDescription => 'double tap';
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoubleFinerTapGestureRecognizer extends GestureRecognizer {
|
||||||
|
DoubleFinerTapGestureRecognizer({
|
||||||
|
Object debugOwner,
|
||||||
|
Set<PointerDeviceKind> supportedDevices,
|
||||||
|
}) : super(
|
||||||
|
debugOwner: debugOwner,
|
||||||
|
supportedDevices: supportedDevices,
|
||||||
|
);
|
||||||
|
|
||||||
|
GestureTapDownCallback onDoubleFinerTapDown;
|
||||||
|
GestureTapDownCallback onDoubleFinerTap;
|
||||||
|
GestureTapCancelCallback onDoubleFinerTapCancel;
|
||||||
|
|
||||||
|
Timer _firstTapTimer; // 第一次点击后的计时 超时未等到第二次操作则reject
|
||||||
|
_TapTracker _firstTap;
|
||||||
|
|
||||||
|
var _isStart = false;
|
||||||
|
|
||||||
|
final Set<int> _upTap = {};
|
||||||
|
|
||||||
|
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isPointerAllowed(PointerDownEvent event) {
|
||||||
|
if (_firstTap == null) {
|
||||||
|
switch (event.buttons) {
|
||||||
|
case kPrimaryButton:
|
||||||
|
if (onDoubleFinerTapDown == null &&
|
||||||
|
onDoubleFinerTap == null &&
|
||||||
|
onDoubleFinerTapCancel == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.isPointerAllowed(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addAllowedPointer(PointerDownEvent event) {
|
||||||
|
// 检测按下事件
|
||||||
|
debugPrint("addAllowedPointer");
|
||||||
|
if (_isStart) {
|
||||||
|
// second
|
||||||
|
if (onDoubleFinerTapDown != null) {
|
||||||
|
final TapDownDetails details = TapDownDetails(
|
||||||
|
globalPosition: event.position,
|
||||||
|
localPosition: event.localPosition,
|
||||||
|
kind: getKindForPointer(event.pointer),
|
||||||
|
);
|
||||||
|
invokeCallback<void>(
|
||||||
|
'onDoubleFinerTapDown', () => onDoubleFinerTapDown(details));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// first tap
|
||||||
|
_isStart = true;
|
||||||
|
_startFirstTapDownTimer();
|
||||||
|
}
|
||||||
|
_trackTap(event); // 捕捉tap
|
||||||
|
}
|
||||||
|
|
||||||
|
void _trackTap(PointerDownEvent event) {
|
||||||
|
final _TapTracker tracker = _TapTracker(
|
||||||
|
event: event,
|
||||||
|
entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
|
||||||
|
doubleTapMinTime: kDoubleTapMinTime,
|
||||||
|
gestureSettings: gestureSettings,
|
||||||
|
);
|
||||||
|
_trackers[event.pointer] = tracker;
|
||||||
|
// debugPrint("_trackers:$_trackers");
|
||||||
|
tracker.startTrackingPointer(_handleEvent, event.transform);
|
||||||
|
|
||||||
|
_registerTap(tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际的逻辑应该是第二次down后一段时间没有抬起则表示start 刚好是双击取反
|
||||||
|
void _handleEvent(PointerEvent event) {
|
||||||
|
final _TapTracker tracker = _trackers[event.pointer];
|
||||||
|
if (event is PointerUpEvent) {
|
||||||
|
debugPrint("PointerUpEvent");
|
||||||
|
_upTap.add(tracker.pointer);
|
||||||
|
} else if (event is PointerMoveEvent) {
|
||||||
|
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop))
|
||||||
|
_reject(tracker);
|
||||||
|
} else if (event is PointerCancelEvent) {
|
||||||
|
_reject(tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void acceptGesture(int pointer) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void rejectGesture(int pointer) {
|
||||||
|
_TapTracker tracker = _trackers[pointer];
|
||||||
|
// If tracker isn't in the list, check if this is the first tap tracker
|
||||||
|
if (tracker == null && _firstTap != null && _firstTap.pointer == pointer) {
|
||||||
|
tracker = _firstTap;
|
||||||
|
}
|
||||||
|
// If tracker is still null, we rejected ourselves already
|
||||||
|
if (tracker != null) {
|
||||||
|
_reject(tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reject(_TapTracker tracker) {
|
||||||
|
_trackers.remove(tracker.pointer);
|
||||||
|
tracker.entry.resolve(GestureDisposition.rejected);
|
||||||
|
_freezeTracker(tracker);
|
||||||
|
if (_firstTap != null) {
|
||||||
|
if (tracker == _firstTap) {
|
||||||
|
_reset();
|
||||||
|
} else {
|
||||||
|
_checkCancel();
|
||||||
|
if (_trackers.isEmpty) {
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_reset();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
_stopFirstTapUpTimer();
|
||||||
|
_firstTap = null;
|
||||||
|
// TODO 正确的释放资源
|
||||||
|
_clearTrackers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerTap(_TapTracker tracker) {
|
||||||
|
GestureBinding.instance.gestureArena.hold(tracker.pointer);
|
||||||
|
// Note, order is important below in order for the clear -> reject logic to
|
||||||
|
// work properly.
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearTrackers() {
|
||||||
|
_trackers.values.toList().forEach(_reject);
|
||||||
|
assert(_trackers.isEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _freezeTracker(_TapTracker tracker) {
|
||||||
|
tracker.stopTrackingPointer(_handleEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startFirstTapDownTimer() {
|
||||||
|
_firstTapTimer ??= Timer(kDoubleTapTimeout, _timeoutCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopFirstTapUpTimer() {
|
||||||
|
if (_firstTapTimer != null) {
|
||||||
|
_firstTapTimer.cancel();
|
||||||
|
_firstTapTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _timeoutCheck() {
|
||||||
|
_isStart = false;
|
||||||
|
if (_upTap.length == 2) {
|
||||||
|
_resolve();
|
||||||
|
} else {
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
_upTap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resolve() {
|
||||||
|
// TODO tap down details
|
||||||
|
if (onDoubleFinerTap != null) onDoubleFinerTap(TapDownDetails());
|
||||||
|
_trackers.forEach((key, value) {
|
||||||
|
value.entry.resolve(GestureDisposition.accepted);
|
||||||
|
});
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkCancel() {
|
||||||
|
if (onDoubleFinerTapCancel != null) {
|
||||||
|
invokeCallback<void>('onHoldDragCancel', onDoubleFinerTapCancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugDescription => 'double tap';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TapTracker helps track individual tap sequences as part of a
|
||||||
|
/// larger gesture.
|
||||||
|
class _TapTracker {
|
||||||
|
_TapTracker({
|
||||||
|
PointerDownEvent event,
|
||||||
|
this.entry,
|
||||||
|
Duration doubleTapMinTime,
|
||||||
|
this.gestureSettings,
|
||||||
|
}) : assert(doubleTapMinTime != null),
|
||||||
|
assert(event != null),
|
||||||
|
assert(event.buttons != null),
|
||||||
|
pointer = event.pointer,
|
||||||
|
_initialGlobalPosition = event.position,
|
||||||
|
initialButtons = event.buttons,
|
||||||
|
_doubleTapMinTimeCountdown =
|
||||||
|
_CountdownZoned(duration: doubleTapMinTime);
|
||||||
|
|
||||||
|
final DeviceGestureSettings gestureSettings;
|
||||||
|
final int pointer;
|
||||||
|
final GestureArenaEntry entry;
|
||||||
|
final Offset _initialGlobalPosition;
|
||||||
|
final int initialButtons;
|
||||||
|
final _CountdownZoned _doubleTapMinTimeCountdown;
|
||||||
|
|
||||||
|
bool _isTrackingPointer = false;
|
||||||
|
|
||||||
|
void startTrackingPointer(PointerRoute route, Matrix4 transform) {
|
||||||
|
if (!_isTrackingPointer) {
|
||||||
|
_isTrackingPointer = true;
|
||||||
|
GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopTrackingPointer(PointerRoute route) {
|
||||||
|
if (_isTrackingPointer) {
|
||||||
|
_isTrackingPointer = false;
|
||||||
|
GestureBinding.instance.pointerRouter.removeRoute(pointer, route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWithinGlobalTolerance(PointerEvent event, double tolerance) {
|
||||||
|
final Offset offset = event.position - _initialGlobalPosition;
|
||||||
|
return offset.distance <= tolerance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasElapsedMinTime() {
|
||||||
|
return _doubleTapMinTimeCountdown.timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasSameButton(PointerDownEvent event) {
|
||||||
|
return event.buttons == initialButtons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CountdownZoned tracks whether the specified duration has elapsed since
|
||||||
|
/// creation, honoring [Zone].
|
||||||
|
class _CountdownZoned {
|
||||||
|
_CountdownZoned({Duration duration}) : assert(duration != null) {
|
||||||
|
Timer(duration, _onTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _timeout = false;
|
||||||
|
|
||||||
|
bool get timeout => _timeout;
|
||||||
|
|
||||||
|
void _onTimeout() {
|
||||||
|
_timeout = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RawGestureDetector getMixinGestureDetector({
|
||||||
|
Widget child,
|
||||||
|
GestureTapUpCallback onTapUp,
|
||||||
|
GestureDoubleTapCallback onDoubleTap,
|
||||||
|
GestureDragStartCallback onHoldDragStart,
|
||||||
|
GestureDragUpdateCallback onHoldDragUpdate,
|
||||||
|
GestureDragCancelCallback onHoldDragCancel,
|
||||||
|
GestureTapDownCallback onDoubleFinerTap,
|
||||||
|
GestureDragStartCallback onOneFingerPanStart,
|
||||||
|
GestureDragUpdateCallback onOneFingerPanUpdate,
|
||||||
|
GestureScaleUpdateCallback onTwoFingerScaleUpdate,
|
||||||
|
GestureDragUpdateCallback onTwoFingerHorizontalDragUpdate,
|
||||||
|
GestureDragUpdateCallback onTwoFingerVerticalDragUpdate,
|
||||||
|
}) {
|
||||||
|
return RawGestureDetector(
|
||||||
|
child: child,
|
||||||
|
gestures: <Type, GestureRecognizerFactory>{
|
||||||
|
// Official
|
||||||
|
TapGestureRecognizer:
|
||||||
|
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||||
|
() => TapGestureRecognizer(), (instance) {
|
||||||
|
instance.onTapUp = onTapUp;
|
||||||
|
}),
|
||||||
|
DoubleTapGestureRecognizer:
|
||||||
|
GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||||||
|
() => DoubleTapGestureRecognizer(), (instance) {
|
||||||
|
instance.onDoubleTap = onDoubleTap;
|
||||||
|
}),
|
||||||
|
// Customized
|
||||||
|
HoldTapMoveGestureRecognizer:
|
||||||
|
GestureRecognizerFactoryWithHandlers<HoldTapMoveGestureRecognizer>(
|
||||||
|
() => HoldTapMoveGestureRecognizer(),
|
||||||
|
(instance) => {
|
||||||
|
instance
|
||||||
|
..onHoldDragStart = onHoldDragStart
|
||||||
|
..onHoldDragUpdate = onHoldDragUpdate
|
||||||
|
..onHoldDragCancel = onHoldDragCancel
|
||||||
|
}),
|
||||||
|
DoubleFinerTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<
|
||||||
|
DoubleFinerTapGestureRecognizer>(
|
||||||
|
() => DoubleFinerTapGestureRecognizer(), (instance) {
|
||||||
|
instance.onDoubleFinerTap = onDoubleFinerTap;
|
||||||
|
}),
|
||||||
|
CustomTouchGestureRecognizer:
|
||||||
|
GestureRecognizerFactoryWithHandlers<CustomTouchGestureRecognizer>(
|
||||||
|
() => CustomTouchGestureRecognizer(), (instance) {
|
||||||
|
instance
|
||||||
|
..onOneFingerPanStart = onOneFingerPanStart
|
||||||
|
..onOneFingerPanUpdate = onOneFingerPanUpdate
|
||||||
|
..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate
|
||||||
|
..onTwoFingerHorizontalDragUpdate = onTwoFingerHorizontalDragUpdate
|
||||||
|
..onTwoFingerVerticalDragUpdate = onTwoFingerVerticalDragUpdate;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
@ -24,7 +24,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
nowCtx = context;
|
currentCtx = context;
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
Timer(Duration(seconds: 5), () {
|
Timer(Duration(seconds: 5), () {
|
||||||
_updateUrl = FFI.getByName('software_update_url');
|
_updateUrl = FFI.getByName('software_update_url');
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -7,6 +8,7 @@ import 'dart:async';
|
|||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
import 'gestures.dart';
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
|
|
||||||
final initText = '\1' * 1024;
|
final initText = '\1' * 1024;
|
||||||
@ -267,7 +269,7 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: isDesktop
|
child: isDesktop
|
||||||
? getBodyForDesktopWithListener()
|
? getBodyForDesktopWithListener()
|
||||||
: SafeArea(child: getBodyForMobileWithGuesture())),
|
: SafeArea(child: getBodyForMobileWithGesture())),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -346,7 +348,50 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getBodyForMobileWithGuesture() {
|
Widget getBodyForMobileWithGesture() {
|
||||||
|
return getMixinGestureDetector(
|
||||||
|
child: getBodyForMobile(),
|
||||||
|
onTapUp: (d) {
|
||||||
|
if (_drag || _scroll) return;
|
||||||
|
if (_touchMode) {
|
||||||
|
FFI.cursorModel.touch(
|
||||||
|
d.localPosition.dx, d.localPosition.dy, _right);
|
||||||
|
} else {
|
||||||
|
FFI.tap(_right);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (_drag || _scroll) return;
|
||||||
|
FFI.tap(_right);
|
||||||
|
FFI.tap(_right);
|
||||||
|
},
|
||||||
|
onDoubleFinerTap: (d) {
|
||||||
|
if (_drag || _scroll) return;
|
||||||
|
FFI.tap(true);
|
||||||
|
},
|
||||||
|
onHoldDragStart: (d) {
|
||||||
|
FFI.sendMouse('down', 'left');
|
||||||
|
},
|
||||||
|
onHoldDragUpdate: (d) {
|
||||||
|
FFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, _touchMode, _drag);
|
||||||
|
},
|
||||||
|
onHoldDragCancel: () {
|
||||||
|
FFI.sendMouse('up', 'left');
|
||||||
|
},
|
||||||
|
onOneFingerPanStart: (d) {
|
||||||
|
FFI.sendMouse('up', 'left');
|
||||||
|
},
|
||||||
|
onOneFingerPanUpdate: (d) {
|
||||||
|
FFI.cursorModel.updatePan(d.delta.dx, d.delta.dy, _touchMode, _drag);
|
||||||
|
},
|
||||||
|
onTwoFingerScaleUpdate: (d) {
|
||||||
|
var scale = (d.scale -1) / 20 + 1;
|
||||||
|
FFI.canvasModel.updateScale(scale);
|
||||||
|
_scale = scale;
|
||||||
|
},
|
||||||
|
onTwoFingerVerticalDragUpdate: (d) {
|
||||||
|
FFI.scroll( - d.delta.dy / 20);
|
||||||
|
});
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (_drag || _scroll) return;
|
if (_drag || _scroll) return;
|
||||||
@ -429,8 +474,8 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
enableSuggestions: false,
|
enableSuggestions: false,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
initialValue:
|
initialValue: _value,
|
||||||
_value, // trick way to make backspace work always
|
// trick way to make backspace work always
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
onChanged: handleInput,
|
onChanged: handleInput,
|
||||||
),
|
),
|
||||||
@ -578,11 +623,10 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
return TextButton(
|
return TextButton(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
minimumSize: Size(0, 0),
|
minimumSize: Size(0, 0),
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 9.75),
|
||||||
vertical: 10,
|
//adds padding inside the button
|
||||||
horizontal: 9.75), //adds padding inside the button
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
tapTargetSize: MaterialTapTargetSize
|
//limits the touch area to the button area
|
||||||
.shrinkWrap, //limits the touch area to the button area
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
),
|
),
|
||||||
|
@ -22,13 +22,13 @@ class ServerPage extends StatelessWidget {
|
|||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text("修改服务ID"),
|
child: Text(translate("Change ID")),
|
||||||
value: "changeID",
|
value: "changeID",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text("修改服务密码"),
|
child: Text("Set your own password"),
|
||||||
value: "changeID",
|
value: "changePW",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
@ -70,7 +70,7 @@ class _ServerInfoState extends State<ServerInfo> {
|
|||||||
// TODO set ID / PASSWORD
|
// TODO set ID / PASSWORD
|
||||||
var _serverId = TextEditingController(text: "");
|
var _serverId = TextEditingController(text: "");
|
||||||
var _serverPasswd = TextEditingController(text: "");
|
var _serverPasswd = TextEditingController(text: "");
|
||||||
static const _emptyIdShow = "正在获取ID...";
|
var _emptyIdShow = translate("connecting_status");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -97,7 +97,7 @@ class _ServerInfoState extends State<ServerInfo> {
|
|||||||
controller: _serverId,
|
controller: _serverId,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
icon: const Icon(Icons.perm_identity),
|
icon: const Icon(Icons.perm_identity),
|
||||||
labelText: '服务ID',
|
labelText: translate("ID"),
|
||||||
labelStyle:
|
labelStyle:
|
||||||
TextStyle(fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
TextStyle(fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
||||||
),
|
),
|
||||||
@ -113,13 +113,12 @@ class _ServerInfoState extends State<ServerInfo> {
|
|||||||
controller: _serverPasswd,
|
controller: _serverPasswd,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
icon: const Icon(Icons.lock),
|
icon: const Icon(Icons.lock),
|
||||||
labelText: '密码',
|
labelText: translate("Password"),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
||||||
suffix: IconButton(
|
suffix: IconButton(
|
||||||
icon: Icon(Icons.visibility),
|
icon: Icon(Icons.visibility),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
debugPrint("icon btn");
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_passwdShow = !_passwdShow;
|
_passwdShow = !_passwdShow;
|
||||||
});
|
});
|
||||||
@ -159,7 +158,7 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
nowCtx = context;
|
currentCtx = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -169,20 +168,20 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
|||||||
return myCard(Column(
|
return myCard(Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
cardTitle("权限列表"),
|
cardTitle(translate("Configuration Permissions")),
|
||||||
PermissionRow("媒体权限", serverModel.mediaOk, _toAndroidInitService),
|
PermissionRow(translate("Media"), serverModel.mediaOk, _toAndroidInitService),
|
||||||
const Divider(height: 0),
|
const Divider(height: 0),
|
||||||
PermissionRow("输入权限", serverModel.inputOk, _toAndroidInitInput),
|
PermissionRow(translate("Input"), serverModel.inputOk, _toAndroidInitInput),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
serverModel.mediaOk
|
serverModel.mediaOk
|
||||||
? ElevatedButton.icon(
|
? ElevatedButton.icon(
|
||||||
icon: Icon(Icons.stop),
|
icon: Icon(Icons.stop),
|
||||||
onPressed: _toAndroidStopService,
|
onPressed: _toAndroidStopService,
|
||||||
label: Text("Stop"))
|
label: Text(translate("Stop service")))
|
||||||
: ElevatedButton.icon(
|
: ElevatedButton.icon(
|
||||||
icon: Icon(Icons.play_arrow),
|
icon: Icon(Icons.play_arrow),
|
||||||
onPressed: _toAndroidInitService,
|
onPressed: _toAndroidInitService,
|
||||||
label: Text("Start")),
|
label: Text(translate("Start Service"))),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -191,17 +190,32 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
|||||||
BuildContext loginReqAlertCtx;
|
BuildContext loginReqAlertCtx;
|
||||||
|
|
||||||
void showLoginReqAlert(BuildContext context, String peerID, String name) async {
|
void showLoginReqAlert(BuildContext context, String peerID, String name) async {
|
||||||
debugPrint("got try_start_without_auth");
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (alertContext) {
|
builder: (alertContext) {
|
||||||
loginReqAlertCtx = alertContext;
|
loginReqAlertCtx = alertContext;
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text("收到连接请求"),
|
title: Text("Control Request"),
|
||||||
content: Text("是否同意来自$name:$peerID的控制?"),
|
content:Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(translate("Do you accept?")),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(child: Text(name[0])),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text(name),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Text(peerID)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text("接受"),
|
child: Text(translate("Accept")),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
FFI.setByName("login_res", "true");
|
FFI.setByName("login_res", "true");
|
||||||
if (!FFI.serverModel.isFileTransfer) {
|
if (!FFI.serverModel.isFileTransfer) {
|
||||||
@ -211,7 +225,7 @@ void showLoginReqAlert(BuildContext context, String peerID, String name) async {
|
|||||||
Navigator.of(alertContext).pop();
|
Navigator.of(alertContext).pop();
|
||||||
}),
|
}),
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text("不接受"),
|
child: Text(translate("Dismiss")),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
FFI.setByName("login_res", "false");
|
FFI.setByName("login_res", "false");
|
||||||
Navigator.of(alertContext).pop();
|
Navigator.of(alertContext).pop();
|
||||||
@ -219,7 +233,6 @@ void showLoginReqAlert(BuildContext context, String peerID, String name) async {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
debugPrint("alert done");
|
|
||||||
loginReqAlertCtx = null;
|
loginReqAlertCtx = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,14 +260,14 @@ class PermissionRow extends StatelessWidget {
|
|||||||
text: name + ": ",
|
text: name + ": ",
|
||||||
style: TextStyle(fontSize: 16.0, color: MyTheme.accent50)),
|
style: TextStyle(fontSize: 16.0, color: MyTheme.accent50)),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: isOk ? "已开启" : "未开启",
|
text: isOk ? translate("ON") : translate("OFF"),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0, color: isOk ? Colors.green : Colors.grey)),
|
fontSize: 16.0, color: isOk ? Colors.green : Colors.grey)),
|
||||||
])),
|
])),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: isOk ? null : onPressed,
|
onPressed: isOk ? null : onPressed,
|
||||||
child: const Text(
|
child: Text(
|
||||||
"去开启",
|
translate("OPEN"),
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
@ -360,7 +373,7 @@ void toAndroidChannelInit() {
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
"pre show loginAlert:${FFI.serverModel.isFileTransfer.toString()}");
|
"pre show loginAlert:${FFI.serverModel.isFileTransfer.toString()}");
|
||||||
showLoginReqAlert(
|
showLoginReqAlert(
|
||||||
nowCtx, FFI.serverModel.peerID, FFI.serverModel.peerName);
|
currentCtx, FFI.serverModel.peerID, FFI.serverModel.peerName);
|
||||||
debugPrint("from jvm:try_start_without_auth done");
|
debugPrint("from jvm:try_start_without_auth done");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user