Implemented settings, new scan, enhanced UI/UX

This commit is contained in:
Dennis Nemec
2025-11-04 16:52:39 +01:00
parent b19a6e1cd4
commit 7ea9108f62
79 changed files with 3306 additions and 566 deletions

View File

@ -0,0 +1,279 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart';
import 'package:hl_lieferservice/feature/scan/presentation/scan_screen.dart';
import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/widget/home/bloc/navigation_bloc.dart';
import 'package:hl_lieferservice/widget/home/bloc/navigation_event.dart';
enum TourHomeSteps { planning, delivery, off }
class ScanPage extends StatefulWidget {
const ScanPage({super.key});
@override
State<StatefulWidget> createState() => _ScanPageState();
}
class _ScanPageState extends State<ScanPage> {
int _currentStepIndex = 0;
@override
void initState() {
super.initState();
_tryFinish(context
.read<TourBloc>()
.state);
}
void _onStartScan() {
Navigator.of(
context,
).push(MaterialPageRoute(builder: (context) => ArticleScanningScreen()));
}
Widget _tourSteps(Tour tour) {
var allArticlesScanned = tour.deliveries.every(
(delivery) => delivery.allArticlesScanned(),
);
return Stepper(
currentStep: _currentStepIndex,
controlsBuilder: (context, details) {
if (details.stepIndex == TourHomeSteps.planning.index) {
return Container(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.only(top: 15),
child: FilledButton.icon(
label: const Text("Scannen"),
onPressed: _onStartScan,
icon: const Icon(Icons.qr_code),
),
),
);
} else {
return Container(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.only(top: 15),
child: FilledButton.icon(
label: const Text("Auslieferung starten"),
onPressed:
allArticlesScanned
? () =>
context.read<NavigationBloc>().add(
NavigateToIndex(index: 1))
: null,
icon: const Icon(Icons.local_shipping),
),
),
);
}
},
onStepContinue:
_currentStepIndex >= 1
? null
: () =>
setState(() {
if (_currentStepIndex < 2) {
_currentStepIndex += 1;
}
}),
onStepCancel:
_currentStepIndex == 0
? null
: () =>
setState(() {
if (_currentStepIndex > 0) {
_currentStepIndex -= 1;
}
}),
onStepTapped:
(value) =>
setState(() {
if (_currentStepIndex == 1 && allArticlesScanned) {
return;
}
_currentStepIndex = value;
}),
steps: [
Step(
title: Row(
children: [
Text(
"Fahrzeuge beladen",
style: TextStyle(
color: allArticlesScanned ? Colors.grey : null,
),
),
Padding(
padding: const EdgeInsets.only(left: 5),
child:
!allArticlesScanned
? const Icon(
Icons.access_time_filled,
color: Colors.orangeAccent,
)
: const Icon(
Icons.check_circle,
color: Colors.lightGreen,
),
),
],
),
content: const Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: 10),
child: Icon(Icons.barcode_reader, color: Colors.black),
),
Text(
"Scannen Sie die Ware, die Sie für die Auslieferungen benötigen.",
),
],
),
),
Step(
title: const Text("Ausliefern"),
content: Container(
alignment: Alignment.centerLeft,
child:
!allArticlesScanned
? const Text(
"Scannen Sie erst die benötigte Ware, um die Auslieferungen zu beginnen.",
)
: null,
),
),
],
);
}
Widget _info(Tour tour) {
int amountArticles = tour.deliveries.fold(
0,
(acc, delivery) =>
acc +
delivery.articles
.where((article) => article.scannable)
.fold(
0,
(amountArticles, article) => amountArticles + article.amount,
),
);
int amountCars = tour.driver.cars.length;
int amountDeliveries = tour.deliveries.length;
return Padding(
padding: const EdgeInsets.all(10),
child: SizedBox(
width: double.infinity,
child: Card(
color: Theme
.of(context)
.colorScheme
.onSecondary,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Padding(
padding: const EdgeInsets.only(top: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.archive),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text("Anzahl Artikel"),
),
],
),
Text(amountArticles.toString()),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.local_shipping_outlined),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text("Anzahl Fahrzeuge"),
),
],
),
Text(amountCars.toString()),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.person),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text("Anzahl Lieferungen"),
),
],
),
Text(amountDeliveries.toString()),
],
),
),
],
),
),
),
),
);
}
void _tryFinish(TourState state) {
if (state is TourLoaded) {
if (state.tour.deliveries.every(
(delivery) => delivery.allArticlesScanned(),
)) {
setState(() {
_currentStepIndex = 1;
});
}
}
}
@override
Widget build(BuildContext context) {
return BlocConsumer<TourBloc, TourState>(
listener: (context, state) {
_tryFinish(state);
},
builder: (context, state) {
if (state is TourLoaded) {
return Column(children: [_info(state.tour), _tourSteps(state.tour)]);
}
return Center(child: CircularProgressIndicator());
},
);
}
}