Final commit.

This commit is contained in:
Dennis Nemec
2026-06-01 17:12:28 +02:00
parent 3ecbc82885
commit a9bf8ecdd1
385 changed files with 29081 additions and 12089 deletions

View File

@ -1,69 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart';
import 'package:hl_lieferservice/feature/car_selection/bloc/state.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart';
import 'package:hl_lieferservice/feature/delivery/model/delivery_phase.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_fail_page.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview.dart';
import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
import '../../bloc/tour_bloc.dart';
import '../../bloc/tour_state.dart';
/// Inhalt der Phase "Ausliefern". Sortieren und Beladen werden über eigene
/// Pages und das Phasen-Routing in `Home` gerendert — diese Page übernimmt
/// nur noch die letzte Phase. Der Phasen-Stepper bleibt sichtbar, damit der
/// Fahrer bei Bedarf zurückspringen kann; das BottomNav der Auslieferung
/// liegt im umgebenden `Home`-Scaffold.
class DeliveryOverviewPage extends StatefulWidget {
class DeliveryOverviewPage extends StatelessWidget {
const DeliveryOverviewPage({super.key});
@override
State<StatefulWidget> createState() => _DeliveryOverviewPageState();
}
class _DeliveryOverviewPageState extends State<DeliveryOverviewPage> {
Widget _buildOverviewWithBanner({
required Tour tour,
required String bannerText,
}) {
return Column(
children: [
Material(
color: Colors.amber.shade100,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
const SizedBox(width: 12),
Expanded(child: Text(bannerText)),
],
),
),
),
Expanded(
child: DeliveryOverview(tour: tour),
),
],
);
}
@override
Widget build(BuildContext context) {
final carState = context.watch<CarSelectBloc>().state;
final carId = carState is CarSelectComplete
? carState.selectedCar.id.toString()
: "";
final carId =
carState is CarSelectComplete ? carState.selectedCar.id : '';
return Scaffold(
// Drawer ist hier ebenfalls aktiv, damit der Menü-Button des Steppers
// konsistent über alle Phasen funktioniert.
drawer: const HomeAppDrawer(),
appBar: PreferredSize(
preferredSize: const Size.fromHeight(140),
@ -72,25 +35,79 @@ class _DeliveryOverviewPageState extends State<DeliveryOverviewPage> {
carId: carId,
),
),
body: BlocBuilder<TourBloc, TourState>(
body: BlocConsumer<TourBloc, TourState>(
listenWhen: (prev, next) =>
next is TourLoaded && next.refreshError != null,
listener: (context, state) {
if (state is TourLoaded && state.refreshError != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.refreshError!)),
);
}
},
builder: (context, state) {
if (state is TourLoaded) {
if (state.distances == null) {
return _buildOverviewWithBanner(
tour: state.tour,
bannerText: "Berechne Distanzen…",
);
}
return DeliveryOverview(tour: state.tour);
switch (state) {
case TourLoaded(:final details):
return _OverviewBody(details: details);
case TourEmpty():
return const _EmptyTourBody();
case TourLoadFailed():
return const DeliveryLoadingFailedPage();
case TourInitial():
case TourLoading():
return const Center(child: CircularProgressIndicator());
}
if (state is TourLoadingFailed) {
return DeliveryLoadingFailedPage();
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
class _OverviewBody extends StatelessWidget {
const _OverviewBody({required this.details});
final TourDetails details;
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
context.read<TourBloc>().add(const RefreshTour());
},
child: DeliveryOverview(details: details),
);
}
}
class _EmptyTourBody extends StatelessWidget {
const _EmptyTourBody();
@override
Widget build(BuildContext context) {
// Wenn der ERP-Sync für heute keine Tour gemeldet hat, ist das ein
// normaler Zustand — kein Fehler. UX-Hinweis und Pull-to-refresh.
return RefreshIndicator(
onRefresh: () async {
context.read<TourBloc>().add(const RefreshTour());
},
child: ListView(
children: const [
SizedBox(height: 120),
Icon(Icons.event_busy, size: 64, color: Colors.grey),
SizedBox(height: 16),
Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Für heute ist keine Tour zugewiesen.\n'
'Zum Aktualisieren nach unten ziehen.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
),
),
],
),
);
}
}