feat: ui rework and gear generator

This commit is contained in:
2026-04-28 17:13:30 +02:00
parent 82ea8125e1
commit 57a14134a6
300 changed files with 2901 additions and 135 deletions

View File

@ -1,5 +1,7 @@
import 'dart:math' as math;
import 'package:abawo_bt_app/model/gear_ratio_codec.dart';
import 'package:abawo_bt_app/widgets/gear_configurator_dialog.dart';
import 'package:flutter/material.dart';
class GearRatioPreset {
@ -76,8 +78,8 @@ class GearRatioEditorCard extends StatefulWidget {
}
class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
static const double _sliderMin = 0.10;
static const double _sliderMax = 3.90;
static const double _sliderMin = gearRatioMin;
static const double _sliderMax = gearRatioMax;
static const double _sliderPivotT = 0.50;
static const double _sliderPivotV = 1.00;
static const Duration _animDuration = Duration(milliseconds: 280);
@ -165,10 +167,22 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
TextStyle(fontSize: 17, fontWeight: FontWeight.w700),
),
),
if (!_isEditing)
IconButton(
tooltip: 'Configure drivetrain',
onPressed: (widget.isLoading ||
widget.errorText != null ||
_isSaving)
? null
: _openGearConfigurator,
icon: const Icon(Icons.settings_input_component_outlined),
),
if (!_isEditing)
IconButton(
tooltip: 'Edit ratios',
onPressed: (widget.isLoading || widget.errorText != null)
onPressed: (widget.isLoading ||
widget.errorText != null ||
_isSaving)
? null
: _enterEditMode,
icon: const Icon(Icons.edit_outlined),
@ -830,6 +844,62 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
_loadPreset(selected);
}
Future<void> _openGearConfigurator() async {
final calculation = await showGearConfiguratorDialog(context);
if (!mounted || calculation == null) {
return;
}
setState(() {
_isSaving = true;
});
final ratios = List<double>.from(calculation.ratios);
final message =
await widget.onSave(ratios, _normalizeDefaultIndex(0, ratios.length));
if (!mounted) {
return;
}
setState(() {
_isSaving = false;
if (message == null) {
_committed = ratios;
_draft = List<double>.from(ratios);
_committedDefaultGearIndex = _normalizeDefaultIndex(0, ratios.length);
_draftDefaultGearIndex = _committedDefaultGearIndex;
_isEditing = false;
_isExpanded = true;
}
});
final messenger = ScaffoldMessenger.of(context);
if (message != null) {
messenger.showSnackBar(SnackBar(content: Text(message)));
return;
}
final notices = <String>[
if (calculation.discardedBelowRange > 0)
'${calculation.discardedBelowRange} below range skipped',
if (calculation.discardedAboveRange > 0)
'${calculation.discardedAboveRange} above range skipped',
if (calculation.duplicateCount > 0)
'${calculation.duplicateCount} duplicates removed',
if (calculation.truncatedCount > 0)
'${calculation.truncatedCount} high gears truncated',
];
messenger.showSnackBar(
SnackBar(
content: Text(
notices.isEmpty
? 'Applied ${ratios.length} calculated gear ratios.'
: 'Applied ${ratios.length} calculated gear ratios (${notices.join(', ')}).',
),
),
);
}
void _applyNamedPreset(String name) {
for (final preset in widget.presets) {
if (preset.name.toLowerCase() == name.toLowerCase()) {
@ -995,8 +1065,7 @@ class _GearRatioEditorCardState extends State<GearRatioEditorCard> {
}
double _quantizeRatio(double raw) {
final clamped = raw.clamp(_sliderMin, _sliderMax);
return ((clamped * 64).round() / 64.0).clamp(_sliderMin, _sliderMax);
return quantizeGearRatio(raw).clamp(_sliderMin, _sliderMax);
}
(List<double>, int) _sortedWithDefault(