From c78bdefc44838301c7588be582a9a10e18a6cdf8 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 21 Aug 2026 14:15:35 +0800 Subject: [PATCH] fix: dialog, trackpad speed, buttons (close -> ok, cancel) (#15918) * fix: dialog, trackpad speed, buttons (close -> ok, cancel) Signed-off-by: fufesou * fix(flutter): handle trackpad speed dialog submission - commit typed values from Enter and OK - validate input before saving - prevent duplicate submissions - surface save failures Signed-off-by: fufesou * fix(flutter): sync trackpad speed input and slider - handle trackpad speed submission from IME actions - update the slider when a valid speed is typed - cover Enter, OK, IME, and invalid input behavior Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/common/widgets/dialog.dart | 118 +++++++++++++++--- .../lib/common/widgets/setting_widgets.dart | 48 +++++-- 2 files changed, 142 insertions(+), 24 deletions(-) diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index f009c051c..98d7f6b4b 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1899,26 +1899,110 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async { msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]); } -trackpadSpeedDialog(SessionID sessionId, FFI ffi) async { - int initSpeed = ffi.inputModel.trackpadSpeed; +int? _validateTrackpadSpeed(String text) { + final speed = int.tryParse(text); + if (speed == null || speed < kMinTrackpadSpeed || speed > kMaxTrackpadSpeed) { + BotToast.showText( + text: + '${translate('Invalid format')}: $kMinTrackpadSpeed-$kMaxTrackpadSpeed', + contentColor: Colors.red, + ); + return null; + } + return speed; +} + +Future _saveTrackpadSpeed({ + required SessionID sessionId, + required FFI ffi, + required int initSpeed, + required int speed, +}) async { + if (speed == initSpeed) { + return; + } + await bind.sessionSetTrackpadSpeed(sessionId: sessionId, value: speed); + await ffi.inputModel.updateTrackpadSpeed(); +} + +void _showTrackpadSpeedSaveError(Object error, StackTrace stackTrace) { + debugPrint('Failed to save trackpad speed: $error'); + debugPrintStack(stackTrace: stackTrace); + BotToast.showText( + text: translate('Failed'), + contentColor: Colors.red, + ); +} + +List _trackpadSpeedDialogActions({ + required bool isSubmitting, + required VoidCallback close, + required VoidCallback submit, +}) { + return [ + dialogButton( + 'Cancel', + icon: Icon(Icons.close_rounded), + onPressed: isSubmitting ? null : close, + isOutline: true, + ), + dialogButton( + 'OK', + icon: Icon(Icons.done_rounded), + onPressed: isSubmitting ? null : submit, + ), + ]; +} + +void trackpadSpeedDialog(SessionID sessionId, FFI ffi) { + final initSpeed = ffi.inputModel.trackpadSpeed; final curSpeed = SimpleWrapper(initSpeed); - final btnClose = dialogButton('Close', onPressed: () async { - if (curSpeed.value <= kMaxTrackpadSpeed && - curSpeed.value >= kMinTrackpadSpeed && - curSpeed.value != initSpeed) { - await bind.sessionSetTrackpadSpeed( - sessionId: sessionId, value: curSpeed.value); - await ffi.inputModel.updateTrackpadSpeed(); + var speedText = initSpeed.toString(); + var isSubmitting = false; + ffi.dialogManager.show((setState, close, context) { + Future submit([String? submittedText]) async { + if (isSubmitting) { + return; + } + speedText = submittedText ?? speedText; + final speed = _validateTrackpadSpeed(speedText); + if (speed == null) { + return; + } + setState(() => isSubmitting = true); + try { + await _saveTrackpadSpeed( + sessionId: sessionId, + ffi: ffi, + initSpeed: initSpeed, + speed: speed, + ); + close(); + } catch (error, stackTrace) { + _showTrackpadSpeedSaveError(error, stackTrace); + setState(() => isSubmitting = false); + } } - ffi.dialogManager.dismissAll(); - }); - msgBoxCommon( - ffi.dialogManager, - 'Trackpad speed', - TrackpadSpeedWidget( - value: curSpeed, + + return CustomAlertDialog( + title: Text( + translate('Trackpad speed'), + style: TextStyle(fontSize: 21), ), - [btnClose]); + content: TrackpadSpeedWidget( + value: curSpeed, + onTextChanged: (text) => speedText = text, + onTextSubmitted: submit, + ), + actions: _trackpadSpeedDialogActions( + isSubmitting: isSubmitting, + close: close, + submit: submit, + ), + onSubmit: isSubmitting ? null : submit, + onCancel: isSubmitting ? null : close, + ); + }); } void deleteConfirmDialog(Function onSubmit, String title) async { diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart index f3be77003..9449c3624 100644 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ b/flutter/lib/common/widgets/setting_widgets.dart @@ -253,8 +253,18 @@ class TrackpadSpeedWidget extends StatefulWidget { final SimpleWrapper value; // If null, no debouncer will be applied. final Function(int)? onDebouncer; + final ValueChanged? onTextChanged; + // IME actions call TextField.onSubmitted without reaching the dialog's + // raw Enter handler, so the dialog needs a separate submission callback. + final ValueChanged? onTextSubmitted; - TrackpadSpeedWidget({Key? key, required this.value, this.onDebouncer}); + TrackpadSpeedWidget({ + Key? key, + required this.value, + this.onDebouncer, + this.onTextChanged, + this.onTextSubmitted, + }); @override TrackpadSpeedWidgetState createState() => TrackpadSpeedWidgetState(); @@ -276,6 +286,34 @@ class TrackpadSpeedWidgetState extends State { debouncerSpeed.setValue(value); } }); + widget.onTextChanged?.call(_controller.text); + } + + void updateTextValue(String text) { + widget.onTextChanged?.call(text); + final newValue = int.tryParse(text); + if (newValue == null || + newValue < kMinTrackpadSpeed || + newValue > kMaxTrackpadSpeed) { + return; + } + setState(() => value = newValue); + } + + void submitTextValue(String text) { + final onTextSubmitted = widget.onTextSubmitted; + if (onTextSubmitted != null) { + onTextSubmitted(text); + return; + } + if (widget.onTextChanged != null) { + return; + } + final newValue = int.tryParse(text); + if (newValue == null) { + return; + } + updateValue(newValue); } @override @@ -315,12 +353,8 @@ class TrackpadSpeedWidgetState extends State { controller: _controller, keyboardType: TextInputType.number, textAlign: TextAlign.center, - onSubmitted: (text) { - int? v = int.tryParse(text); - if (v != null) { - updateValue(v); - } - }, + onChanged: updateTextValue, + onSubmitted: submitTextValue, style: const TextStyle(fontSize: 13), decoration: InputDecoration( contentPadding: