Log mission name on send, log mission/drone-profile changes, auto-send on mission switch
Include the mission name in the "Mission sent"/"Mission upload failed" System Messages (was just the waypoint count before). Add MissionMeta.switchSeq, bumped only by an actual mission switch (startNew/startNewFromPlace/loadMission) - not by the id a brand-new mission gets from its first autosave, and not by restoreLastSession() on app start. FlyScreen compares it to detect a genuine switch and reacts two ways: logs "Mission changed to ..." and automatically re-uploads the new route to the flight controller, reusing the same send path as the manual send button (same _sending guard, same success/failure snackbar and log entry). Mission-change and drone-profile-change logging intentionally live in FlyScreen's ref.listen callbacks, not in mission_meta_provider.dart / active_drone_profile_provider.dart themselves - those providers have no notion of the app mode (Plan vs Fly, tracked separately by AppModeCubit), and logging there would record a change regardless of mode. Since FlyScreen only exists while Fly mode is active, scoping the listeners there means switching missions or drone profiles from Plan mode produces no System Messages entries, and only a mission switch (not a drone profile switch) triggers the automatic re-upload, matching what was asked for. Also splits systemMessageLogProvider (the plain message list + log(), no telemetry dependency) from the new systemMessageAutoLogProvider (the Connected/lost/battery/failsafe/connection-type auto-detection, which does watch telemetryProvider) - discovered while wiring the mission-change logging that logging a plain message from Plan-mode code was forcing the entire telemetry/transport stack to spin up as a side effect, which broke an unrelated Plan-mode test (UdpTransport threw on a double-disconnect during teardown). Keeping the two concerns apart means calling log() for a one-off message never has that side effect. Verified end to end on the Pixel_10a emulator: switching to an empty mission while in Fly mode correctly showed "No waypoints to send" and logged "Mission changed to ..." automatically, without touching the send button. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b09d00ab9f
commit
b68c16d8e6
@@ -19,11 +19,14 @@ import 'package:dmc_app/services/sharing/drone_share_codec.dart';
|
||||
import 'package:dmc_app/services/sharing/mission_share_codec.dart';
|
||||
import 'package:dmc_app/services/sharing/sharing_service.dart';
|
||||
import 'package:dmc_app/ui/app_shell.dart';
|
||||
import 'package:dmc_app/ui/screens/fly/fly_screen.dart';
|
||||
import 'package:dmc_app/ui/providers/active_drone_profile_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/current_mission_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/database_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/geocoding_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/mission_meta_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/sharing_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/system_message_log_provider.dart';
|
||||
import 'package:dmc_app/ui/providers/telemetry_provider.dart';
|
||||
import 'package:dmc_app/transport/mock/mock_flight_controller_link.dart';
|
||||
import 'package:dmc_app/ui/widgets/drone_follow_icon.dart';
|
||||
@@ -671,6 +674,121 @@ void main() {
|
||||
expect(camera.zoom, 14);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Missionswechsel im Fly-Modus sendet die neue Route automatisch an '
|
||||
'den Flightcontroller', (tester) async {
|
||||
final container = _testContainer();
|
||||
final appModeCubit = AppModeCubit();
|
||||
addTearDown(() {
|
||||
container.dispose();
|
||||
appModeCubit.close();
|
||||
});
|
||||
final missionBId = await container.read(missionRepositoryProvider).upsert(
|
||||
name: 'Mission B',
|
||||
waypoints: const [
|
||||
Waypoint(lat: 10, lon: 10, altitudeM: 60, speedMs: 15, catchRadiusM: 60),
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: BlocProvider<AppModeCubit>.value(
|
||||
value: appModeCubit,
|
||||
child: const MaterialApp(home: AppShell()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
appModeCubit.toFly();
|
||||
// Zwei Frames noetig, bis AppShells context.select(AppModeCubit) auf den
|
||||
// Moduswechsel reagiert und FlyScreen tatsaechlich mountet (ein einzelner
|
||||
// pump() reicht hier nicht, anders als beim Antippen des Fly-Buttons in
|
||||
// anderen Tests, wo der Tap selbst schon einen Zwischenschritt einbringt).
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(find.byType(FlyScreen), findsOneWidget);
|
||||
|
||||
await container.read(currentMissionMetaProvider.notifier).loadMission(missionBId);
|
||||
// syncAndUpload() durchlaeuft mehrere awaits (connect/upload/verify) auf
|
||||
// dem Mock, alle ohne echte Timer - ein paar Frames reichen zum
|
||||
// Durchlaufen, bewusst kein pumpAndSettle() (siehe andere Fly-Modus-
|
||||
// Tests: der periodische Mock-Telemetrie-Timer wuerde das nie "settlen"
|
||||
// lassen).
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
expect(appModeCubit.state.missionUploaded, isTrue);
|
||||
expect(appModeCubit.state.missionVerified, isTrue);
|
||||
expect(find.textContaining('Mission sent'), findsOneWidget);
|
||||
|
||||
final log = container.read(systemMessageLogProvider);
|
||||
expect(log.map((e) => e.message), contains('Mission changed to "Mission B"'));
|
||||
expect(
|
||||
log.map((e) => e.message),
|
||||
contains(predicate<String>((m) => m.startsWith('Mission sent: "Mission B"'))),
|
||||
);
|
||||
|
||||
await _stopTelemetry(container);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Missionswechsel im Plan-Modus wird NICHT im System-Messages-Log erfasst '
|
||||
'(nur im Fly-Modus, Doku: "nur im fly modus loggen")', (tester) async {
|
||||
final container = _testContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: BlocProvider(
|
||||
create: (_) => AppModeCubit(),
|
||||
child: const MaterialApp(home: AppShell()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await container.read(currentMissionMetaProvider.notifier).startNewFromPlace('Wien');
|
||||
await tester.pump();
|
||||
|
||||
expect(container.read(systemMessageLogProvider), isEmpty);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Drohnenprofilwechsel im Fly-Modus wird im System-Messages-Log erfasst',
|
||||
(tester) async {
|
||||
final container = _testContainer();
|
||||
addTearDown(container.dispose);
|
||||
final otherProfile = DroneProfile.t1Ranger().copyWith(id: 99, name: 'Racer');
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: BlocProvider(
|
||||
create: (_) => AppModeCubit(),
|
||||
child: const MaterialApp(home: AppShell()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('Fly'));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(find.byType(FlyScreen), findsOneWidget);
|
||||
|
||||
await container.read(activeDroneProfileProvider.notifier).select(otherProfile);
|
||||
await tester.pump();
|
||||
|
||||
final log = container.read(systemMessageLogProvider);
|
||||
expect(log.map((e) => e.message), contains('Drone profile changed to "Racer"'));
|
||||
|
||||
await _stopTelemetry(container);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Wegpunktliste scrollt beim Oeffnen automatisch zum aktiven Wegpunkt',
|
||||
(tester) async {
|
||||
|
||||
Reference in New Issue
Block a user