feat(ui): add gear preset shortcuts
This commit is contained in:
@ -806,6 +806,63 @@ class _DeviceDetailsPageState extends ConsumerState<DeviceDetailsPage> {
|
|||||||
_isFirmwareUpdateBusy ? null : _loadGearRatios,
|
_isFirmwareUpdateBusy ? null : _loadGearRatios,
|
||||||
onSave: _saveGearRatios,
|
onSave: _saveGearRatios,
|
||||||
presets: const [
|
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(
|
GearRatioPreset(
|
||||||
name: 'KeAnt Classic',
|
name: 'KeAnt Classic',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -14,6 +14,42 @@ class GearRatioPreset {
|
|||||||
final List<double> ratios;
|
final List<double> 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 {
|
class GearRatioEditorCard extends StatefulWidget {
|
||||||
const GearRatioEditorCard({
|
const GearRatioEditorCard({
|
||||||
required this.ratios,
|
required this.ratios,
|
||||||
@ -60,6 +96,12 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
|
|||||||
List<double>? _stretchBase;
|
List<double>? _stretchBase;
|
||||||
int _gearLayoutVersion = 0;
|
int _gearLayoutVersion = 0;
|
||||||
|
|
||||||
|
static const List<String> _quickPresetNames = [
|
||||||
|
'Road',
|
||||||
|
'Gravel',
|
||||||
|
'MTB',
|
||||||
|
];
|
||||||
|
|
||||||
List<double> _committed = const [];
|
List<double> _committed = const [];
|
||||||
List<double> _draft = const [];
|
List<double> _draft = const [];
|
||||||
int _committedDefaultGearIndex = 0;
|
int _committedDefaultGearIndex = 0;
|
||||||
@ -235,6 +277,25 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
|
|||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
if (_isEditing) ...[
|
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(
|
||||||
padding: const EdgeInsets.fromLTRB(14, 0, 14, 8),
|
padding: const EdgeInsets.fromLTRB(14, 0, 14, 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -251,12 +312,6 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: _openPresetPicker,
|
|
||||||
icon: const Icon(Icons.tune),
|
|
||||||
label: const Text('Load preset'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -749,8 +804,21 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
|
|||||||
return;
|
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(() {
|
setState(() {
|
||||||
_draft = selected.ratios.map(_quantizeRatio).toList(growable: false);
|
_draft = preset.ratios.map(_quantizeRatio).toList(growable: false);
|
||||||
_draftDefaultGearIndex = _normalizeDefaultIndex(0, _draft.length);
|
_draftDefaultGearIndex = _normalizeDefaultIndex(0, _draft.length);
|
||||||
if (_sortAscending) {
|
if (_sortAscending) {
|
||||||
_sortDraft(animate: true);
|
_sortDraft(animate: true);
|
||||||
|
|||||||
Reference in New Issue
Block a user