feat: offer to calibrate sensor after provisioning

This commit is contained in:
2026-05-22 14:43:19 +02:00
parent 5c90c7f514
commit f6c6315596
@@ -4,7 +4,9 @@ import 'package:go_router/go_router.dart';
import '../../data/sources/ble/ble_provisioner.dart'; import '../../data/sources/ble/ble_provisioner.dart';
import '../../data/sources/local/credential_store.dart'; import '../../data/sources/local/credential_store.dart';
import '../../domain/models/sensor.dart';
import '../../providers.dart'; import '../../providers.dart';
import '../sensors/calibration_sheet.dart';
enum _Step { scan, configure, done } enum _Step { scan, configure, done }
@@ -30,6 +32,7 @@ class _BleProvisionSheetState extends ConsumerState<BleProvisionSheet> {
_Step _step = _Step.scan; _Step _step = _Step.scan;
BleScanResult? _selected; BleScanResult? _selected;
Sensor? _provisionedSensor;
bool _provisioning = false; bool _provisioning = false;
bool _mqttOverrideEnabled = false; bool _mqttOverrideEnabled = false;
bool _usingNewWifiConnection = true; bool _usingNewWifiConnection = true;
@@ -102,7 +105,7 @@ class _BleProvisionSheetState extends ConsumerState<BleProvisionSheet> {
mqttHost: mqttHost, mqttHost: mqttHost,
mqttPort: mqttPort, mqttPort: mqttPort,
); );
await ref _provisionedSensor = await ref
.read(sensorRepositoryProvider) .read(sensorRepositoryProvider)
.createSensor(_selected!.name, name: _selected!.name); .createSensor(_selected!.name, name: _selected!.name);
@@ -254,12 +257,26 @@ class _BleProvisionSheetState extends ConsumerState<BleProvisionSheet> {
), ),
_DoneStep( _DoneStep(
onGoToFloorPlan: () { onGoToFloorPlan: () {
final router = GoRouter.of(context); if (_provisionedSensor != null) {
Navigator.of(context).pop(); _placeOnFloorPlan(context, _provisionedSensor!);
router.go('/floorplan'); }
}, },
onAddAnother: _reset, onAddAnother: _reset,
onDone: () => Navigator.of(context).pop(), onDone: () => Navigator.of(context).pop(),
onCalibrate: _provisionedSensor == null
? null
: () {
final sensor = _provisionedSensor!;
Navigator.of(
context,
rootNavigator: true,
).pop();
showCalibrationSheet(
context,
sensor.id,
sensor.sensorId,
);
},
), ),
] ]
.map( .map(
@@ -276,6 +293,16 @@ class _BleProvisionSheetState extends ConsumerState<BleProvisionSheet> {
), ),
); );
} }
void _placeOnFloorPlan(BuildContext context, Sensor sensor) {
final router = GoRouter.of(context);
final fromSensors =
router.routeInformationProvider.value.uri.path == '/sensors';
ref.read(sensorPlacementProvider.notifier).state = sensor;
ref.read(sensorPlacementOriginSensorsProvider.notifier).state = fromSensors;
Navigator.of(context, rootNavigator: true).pop();
router.go('/floorplan');
}
} }
class _ScanPage extends StatefulWidget { class _ScanPage extends StatefulWidget {
@@ -491,9 +518,7 @@ class _ConfigurePageState extends State<_ConfigurePage> {
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_obscurePassword _obscurePassword ? Icons.visibility_off : Icons.visibility,
? Icons.visibility_off
: Icons.visibility,
), ),
onPressed: () => onPressed: () =>
setState(() => _obscurePassword = !_obscurePassword), setState(() => _obscurePassword = !_obscurePassword),
@@ -595,14 +620,17 @@ class _DoneStep extends StatelessWidget {
required this.onGoToFloorPlan, required this.onGoToFloorPlan,
required this.onAddAnother, required this.onAddAnother,
required this.onDone, required this.onDone,
this.onCalibrate,
}); });
final VoidCallback onGoToFloorPlan; final VoidCallback onGoToFloorPlan;
final VoidCallback onAddAnother; final VoidCallback onAddAnother;
final VoidCallback onDone; final VoidCallback onDone;
final VoidCallback? onCalibrate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Column( child: Column(
@@ -613,22 +641,36 @@ class _DoneStep extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Sensor provisioned!', 'Sensor provisioned!',
style: Theme.of(context).textTheme.titleLarge, style: theme.textTheme.titleLarge,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'The sensor will appear in the list once it connects to the network. ' 'The sensor will appear in the list once it connects to the network. '
'Open the floor plan to place it.', 'Open the floor plan to place it.',
style: Theme.of(context).textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
FilledButton.icon( FilledButton.icon(
icon: const Icon(Icons.map_outlined), icon: const Icon(Icons.gps_fixed),
label: const Text('Place on floor plan'), label: const Text('Place on floor plan'),
onPressed: onGoToFloorPlan, onPressed: onGoToFloorPlan,
), ),
const SizedBox(height: 16),
OutlinedButton.icon(
icon: const Icon(Icons.tune),
label: const Text('Calibrate sensor'),
onPressed: onCalibrate,
),
const SizedBox(height: 8),
Text(
'For best accuracy, calibrate after mounting the sensor in its intended position.',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 12), const SizedBox(height: 12),
OutlinedButton.icon( OutlinedButton.icon(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),