From 16ac66471a8d8dae8ca98534a9d03775961deeae Mon Sep 17 00:00:00 2001 From: Yandrik Date: Thu, 23 Apr 2026 22:37:52 +0200 Subject: [PATCH] feat(ui): add gear preset shortcuts --- lib/pages/device_details_page.dart | 57 +++++++++++++++++ lib/widgets/gear_ratio_editor_card.dart | 82 ++++++++++++++++++++++--- 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/lib/pages/device_details_page.dart b/lib/pages/device_details_page.dart index 13a81df..b0f6001 100644 --- a/lib/pages/device_details_page.dart +++ b/lib/pages/device_details_page.dart @@ -806,6 +806,63 @@ class _DeviceDetailsPageState extends ConsumerState { _isFirmwareUpdateBusy ? null : _loadGearRatios, onSave: _saveGearRatios, presets: const [ + GearRatioPreset( + name: 'Road', + description: + 'Balanced 12-speed road gearing for steady cadence steps.', + ratios: [ + 0.50, + 0.58, + 0.67, + 0.76, + 0.86, + 0.97, + 1.09, + 1.22, + 1.36, + 1.51, + 1.67, + 1.84, + ], + ), + GearRatioPreset( + name: 'Gravel', + description: + 'Slightly lower gearing with smooth jumps for mixed terrain rides.', + ratios: [ + 0.46, + 0.54, + 0.62, + 0.70, + 0.79, + 0.89, + 1.00, + 1.12, + 1.25, + 1.40, + 1.57, + 1.76, + ], + ), + GearRatioPreset( + name: 'MTB', + description: + 'Lower climbing gears with wider top-end spacing for steep trails.', + ratios: [ + 0.42, + 0.49, + 0.57, + 0.66, + 0.76, + 0.87, + 1.00, + 1.15, + 1.32, + 1.52, + 1.75, + 2.02, + ], + ), GearRatioPreset( name: 'KeAnt Classic', description: diff --git a/lib/widgets/gear_ratio_editor_card.dart b/lib/widgets/gear_ratio_editor_card.dart index 23e2b71..f3f9382 100644 --- a/lib/widgets/gear_ratio_editor_card.dart +++ b/lib/widgets/gear_ratio_editor_card.dart @@ -14,6 +14,42 @@ class GearRatioPreset { final List ratios; } +class _QuickPresetButton extends StatelessWidget { + const _QuickPresetButton({ + required this.label, + required this.onPressed, + this.icon, + }); + + final String label; + final VoidCallback onPressed; + final IconData? icon; + + @override + Widget build(BuildContext context) { + final buttonStyle = OutlinedButton.styleFrom( + minimumSize: const Size(0, 42), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + ); + + if (icon != null) { + return OutlinedButton.icon( + onPressed: onPressed, + style: buttonStyle, + icon: Icon(icon, size: 18), + label: Text(label), + ); + } + + return OutlinedButton( + onPressed: onPressed, + style: buttonStyle, + child: Text(label), + ); + } +} + class GearRatioEditorCard extends StatefulWidget { const GearRatioEditorCard({ required this.ratios, @@ -60,6 +96,12 @@ class _GearRatioEditorCardState extends State { List? _stretchBase; int _gearLayoutVersion = 0; + static const List _quickPresetNames = [ + 'Road', + 'Gravel', + 'MTB', + ]; + List _committed = const []; List _draft = const []; int _committedDefaultGearIndex = 0; @@ -235,6 +277,25 @@ class _GearRatioEditorCardState extends State { : const SizedBox.shrink(), ), if (_isEditing) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(14, 0, 14, 10), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final presetName in _quickPresetNames) + _QuickPresetButton( + label: presetName, + onPressed: () => _applyNamedPreset(presetName), + ), + _QuickPresetButton( + label: 'Load', + icon: Icons.folder_open_rounded, + onPressed: _openPresetPicker, + ), + ], + ), + ), Padding( padding: const EdgeInsets.fromLTRB(14, 0, 14, 8), child: Row( @@ -251,12 +312,6 @@ class _GearRatioEditorCardState extends State { }); }, ), - const Spacer(), - OutlinedButton.icon( - onPressed: _openPresetPicker, - icon: const Icon(Icons.tune), - label: const Text('Load preset'), - ), ], ), ), @@ -749,8 +804,21 @@ class _GearRatioEditorCardState extends State { return; } + _loadPreset(selected); + } + + void _applyNamedPreset(String name) { + for (final preset in widget.presets) { + if (preset.name.toLowerCase() == name.toLowerCase()) { + _loadPreset(preset); + return; + } + } + } + + void _loadPreset(GearRatioPreset preset) { setState(() { - _draft = selected.ratios.map(_quantizeRatio).toList(growable: false); + _draft = preset.ratios.map(_quantizeRatio).toList(growable: false); _draftDefaultGearIndex = _normalizeDefaultIndex(0, _draft.length); if (_sortAscending) { _sortDraft(animate: true);