diff --git a/lib/features/onboarding/steps/step_floor_plan.dart b/lib/features/onboarding/steps/step_floor_plan.dart index a3f6d49..cb59bd3 100644 --- a/lib/features/onboarding/steps/step_floor_plan.dart +++ b/lib/features/onboarding/steps/step_floor_plan.dart @@ -1,30 +1,158 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class StepFloorPlan extends StatelessWidget { +import '../../../domain/models/floor_plan_mode.dart'; +import '../../../providers.dart'; +import '../../floorplan/widgets/konva_web_view.dart'; + +class StepFloorPlan extends ConsumerStatefulWidget { const StepFloorPlan({super.key, required this.onComplete}); final VoidCallback onComplete; + @override + ConsumerState createState() => _StepFloorPlanState(); +} + +class _StepFloorPlanState extends ConsumerState { + final _konvaKey = GlobalKey(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(roomsProvider.future).then( + (rooms) => _konvaKey.currentState?.loadFloorPlan(rooms), + ); + _konvaKey.currentState?.setMode(FloorPlanMode.edit); + }); + } + @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text('Draw floor plan', + ref.listen(roomsProvider, (_, next) { + next.whenData((r) => _konvaKey.currentState?.loadFloorPlan(r)); + }); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), + child: Text('Draw floor plan', style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 16), - // TODO: embed KonvaWebView in editor mode (no live overlays). - // User draws rooms, sets scale, then taps Continue. - const Expanded(child: Placeholder()), - const SizedBox(height: 16), - FilledButton( - onPressed: onComplete, + ), + Expanded( + child: KonvaWebView( + key: _konvaKey, + mode: FloorPlanMode.edit, + onSensorTapped: (_) {}, + onSensorMoved: (_, __, ___, ____) {}, + onRoomsUpdated: (roomUpdates) async { + final floor = ref.read(floorProvider).valueOrNull; + if (floor == null) return; + final repo = ref.read(floorRepositoryProvider); + for (final r in roomUpdates) { + await repo.updateRoom(floor.id, r.id, x: r.x, y: r.y); + } + ref.invalidate(roomsProvider); + }, + onRoomAdded: (x, y) => _showAddRoomDialog(x, y), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: FilledButton( + onPressed: widget.onComplete, child: const Text('Continue'), ), + ), + ], + ); + } + + Future _showAddRoomDialog(double x, double y) async { + final nameCtrl = TextEditingController(); + final widthCtrl = TextEditingController(text: '5.0'); + final heightCtrl = TextEditingController(text: '4.0'); + + final confirmed = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Add room'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameCtrl, + decoration: const InputDecoration(labelText: 'Name'), + autofocus: true, + onSubmitted: (_) => Navigator.of(ctx).pop(true), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: TextField( + controller: widthCtrl, + decoration: const InputDecoration( + labelText: 'Width', suffixText: 'm'), + keyboardType: const TextInputType.numberWithOptions( + decimal: true), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextField( + controller: heightCtrl, + decoration: const InputDecoration( + labelText: 'Height', suffixText: 'm'), + keyboardType: const TextInputType.numberWithOptions( + decimal: true), + ), + ), + ], + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: const Text('Add'), + ), ], ), ); + + final name = nameCtrl.text.trim(); + final width = double.tryParse(widthCtrl.text) ?? 5.0; + final height = double.tryParse(heightCtrl.text) ?? 4.0; + nameCtrl.dispose(); + widthCtrl.dispose(); + heightCtrl.dispose(); + + if (confirmed != true || !mounted) return; + if (name.isEmpty) return; + + var floor = ref.read(floorProvider).valueOrNull; + if (floor == null) { + floor = await ref + .read(floorRepositoryProvider) + .createFloor(name: 'Ground Floor'); + ref.invalidate(floorProvider); + } + + await ref.read(floorRepositoryProvider).createRoom( + floor.id, + name: name, + width: width, + height: height, + x: x, + y: y, + ); + ref.invalidate(roomsProvider); } }