diff --git a/app/backend/node_manager.py b/app/backend/node_manager.py index 1c9f9d74..61d2255b 100644 --- a/app/backend/node_manager.py +++ b/app/backend/node_manager.py @@ -914,7 +914,14 @@ def cmd_send_pois(self, poi_ids: list[int]): return with open(pois_file) as f: all_pois = json.load(f) - payload = {str(pid): all_pois[str(pid)] for pid in poi_ids if str(pid) in all_pois} + # Re-index as a dense queue ("0", "1", ...) so downstream + # consumers navigate in the same order the UI sent the checked POIs, + # instead of falling back to the original ids / pois.json order. + payload = {} + for pid in poi_ids: + key = str(pid) + if key in all_pois: + payload[str(len(payload))] = all_pois[key] self._cmd_pois_pub.publish(String(data=json.dumps(payload))) with self._lock: nav_running = self._nav_nodes_running diff --git a/app/frontend/lib/pages/operate_tab.dart b/app/frontend/lib/pages/operate_tab.dart index 06081965..40d84581 100644 --- a/app/frontend/lib/pages/operate_tab.dart +++ b/app/frontend/lib/pages/operate_tab.dart @@ -628,7 +628,8 @@ class _PoiSheet extends ConsumerStatefulWidget { } class _PoiSheetState extends ConsumerState<_PoiSheet> { - final Set _checkedIds = {}; + /// POI ids in the exact order they were checked. + final List _checkedIds = []; Future _deletePoi(Poi poi) async { final ok = await showDialog( @@ -662,7 +663,8 @@ class _PoiSheetState extends ConsumerState<_PoiSheet> { } Future _startNav(List pois) async { - final selectedPois = pois.where((p) => _checkedIds.contains(p.id)).toList(); + final poiById = {for (final poi in pois) poi.id: poi}; + final selectedPois = _checkedIds.map((id) => poiById[id]).whereType().toList(); if (selectedPois.isEmpty) return; try { await ref.read(dioProvider).post( @@ -734,15 +736,24 @@ class _PoiSheetState extends ConsumerState<_PoiSheet> { ) : Column( children: pois - .map((poi) => _PoiTile( - poi: poi, - checked: _checkedIds.contains(poi.id), - onChecked: (v) => setState(() { - if (v) _checkedIds.add(poi.id); - else _checkedIds.remove(poi.id); - }), - onDelete: () => _deletePoi(poi), - )) + .map((poi) { + final orderIndex = _checkedIds.indexOf(poi.id); + return _PoiTile( + poi: poi, + checked: orderIndex != -1, + orderNumber: orderIndex == -1 ? null : orderIndex + 1, + onChecked: (v) => setState(() { + if (v) { + if (!_checkedIds.contains(poi.id)) { + _checkedIds.add(poi.id); + } + } else { + _checkedIds.remove(poi.id); + } + }), + onDelete: () => _deletePoi(poi), + ); + }) .toList(), ), loading: () => const Center(child: CircularProgressIndicator()), @@ -757,12 +768,14 @@ class _PoiSheetState extends ConsumerState<_PoiSheet> { class _PoiTile extends StatelessWidget { final Poi poi; final bool checked; + final int? orderNumber; final ValueChanged onChecked; final VoidCallback onDelete; const _PoiTile({ required this.poi, required this.checked, + required this.orderNumber, required this.onChecked, required this.onDelete, }); @@ -774,7 +787,7 @@ class _PoiTile extends StatelessWidget { value: checked, onChanged: (v) => onChecked(v ?? false), ), - title: Text(poi.name), + title: Text(orderNumber == null ? poi.name : '${poi.name} #$orderNumber'), subtitle: Text( '(${poi.x.toStringAsFixed(2)}, ${poi.y.toStringAsFixed(2)})', style: const TextStyle(fontSize: 12),