374 lines
11 KiB
Dart
374 lines
11 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart';
|
|
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart';
|
|
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_state.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hl_lieferservice/model/delivery.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:signature/signature.dart';
|
|
|
|
enum _SigningPhase { customerAcceptance, customerSignature, driverSignature }
|
|
|
|
class SignatureView extends StatefulWidget {
|
|
const SignatureView({
|
|
super.key,
|
|
required this.onSigned,
|
|
required this.delivery,
|
|
});
|
|
|
|
final Delivery delivery;
|
|
|
|
/// Callback that is called when the user has signed.
|
|
/// The parameter stores the path to the image file of the signature.
|
|
final void Function(
|
|
Uint8List customerSignaturePng,
|
|
Uint8List driverSignaturePng,
|
|
)
|
|
onSigned;
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _SignatureViewState();
|
|
}
|
|
|
|
class _SignatureViewState extends State<SignatureView> {
|
|
final SignatureController _customerController = SignatureController(
|
|
penStrokeWidth: 5,
|
|
penColor: Colors.black,
|
|
exportBackgroundColor: Colors.white,
|
|
);
|
|
|
|
final SignatureController _driverController = SignatureController(
|
|
penStrokeWidth: 5,
|
|
penColor: Colors.black,
|
|
exportBackgroundColor: Colors.white,
|
|
);
|
|
|
|
_SigningPhase _phase = _SigningPhase.customerAcceptance;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
context.read<NoteBloc>().add(LoadNote(delivery: widget.delivery));
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_customerController.dispose();
|
|
_driverController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onAcceptanceDone() {
|
|
setState(() => _phase = _SigningPhase.customerSignature);
|
|
}
|
|
|
|
void _onCustomerSigned() {
|
|
setState(() => _phase = _SigningPhase.driverSignature);
|
|
}
|
|
|
|
Future<void> _onDriverSigned() async {
|
|
widget.onSigned(
|
|
(await _customerController.toPngBytes())!,
|
|
(await _driverController.toPngBytes())!,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return switch (_phase) {
|
|
_SigningPhase.customerAcceptance => _AcceptanceStep(
|
|
onContinue: _onAcceptanceDone,
|
|
),
|
|
_SigningPhase.customerSignature => _SignaturePadStep(
|
|
controller: _customerController,
|
|
delivery: widget.delivery,
|
|
appBarTitle: "Unterschrift des Kunden",
|
|
buttonLabel: "Weiter",
|
|
onContinue: _onCustomerSigned,
|
|
),
|
|
_SigningPhase.driverSignature => _SignaturePadStep(
|
|
controller: _driverController,
|
|
delivery: widget.delivery,
|
|
appBarTitle: "Unterschrift des Fahrers",
|
|
buttonLabel: "Absenden",
|
|
onContinue: _onDriverSigned,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
class _AcceptanceStep extends StatefulWidget {
|
|
const _AcceptanceStep({required this.onContinue});
|
|
|
|
final VoidCallback onContinue;
|
|
|
|
@override
|
|
State<_AcceptanceStep> createState() => _AcceptanceStepState();
|
|
}
|
|
|
|
class _AcceptanceStepState extends State<_AcceptanceStep> {
|
|
bool _customerAccepted = false;
|
|
bool _noteAccepted = false;
|
|
|
|
Widget _notesContent(NoteState noteState) {
|
|
if (noteState is! NoteLoaded) {
|
|
return const SizedBox(
|
|
width: double.infinity,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
if (noteState.notes.isEmpty) {
|
|
return const SizedBox(
|
|
width: double.infinity,
|
|
child: Center(child: Text("Keine Notizen vorhanden")),
|
|
);
|
|
}
|
|
return ListView.separated(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemBuilder: (context, index) {
|
|
return ListTile(
|
|
leading: const Icon(Icons.event_note_outlined),
|
|
title: Text(noteState.notes[index].content),
|
|
contentPadding: const EdgeInsets.all(20),
|
|
tileColor: Theme.of(context).colorScheme.onSecondary,
|
|
);
|
|
},
|
|
separatorBuilder: (context, index) => const Divider(height: 0),
|
|
itemCount: noteState.notes.length,
|
|
);
|
|
}
|
|
|
|
Widget _notes(NoteState noteState) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 15),
|
|
child: Text(
|
|
"Notizen",
|
|
style: Theme.of(context).textTheme.headlineSmall,
|
|
),
|
|
),
|
|
_notesContent(noteState),
|
|
const Padding(padding: EdgeInsets.only(top: 25), child: Divider()),
|
|
],
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return BlocBuilder<NoteBloc, NoteState>(
|
|
builder: (context, noteState) {
|
|
final notesEmpty = switch (noteState) {
|
|
NoteLoadedBase(notes: final ns) => ns.isEmpty,
|
|
_ => true,
|
|
};
|
|
final isButtonEnabled =
|
|
_customerAccepted && (_noteAccepted || notesEmpty);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text("Unterschrift des Kunden")),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: ListView(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 25, bottom: 0),
|
|
child: _notes(noteState),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 25.0, bottom: 0),
|
|
child: Row(
|
|
children: [
|
|
Checkbox(
|
|
value: _noteAccepted,
|
|
onChanged: notesEmpty
|
|
? null
|
|
: (value) {
|
|
setState(() {
|
|
_noteAccepted = value!;
|
|
});
|
|
},
|
|
),
|
|
Flexible(
|
|
child: InkWell(
|
|
onTap: notesEmpty
|
|
? null
|
|
: () {
|
|
setState(() {
|
|
_noteAccepted = !_noteAccepted;
|
|
});
|
|
},
|
|
child: Text(
|
|
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
|
|
overflow: TextOverflow.fade,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 25.0, bottom: 10.0),
|
|
child: Row(
|
|
children: [
|
|
Checkbox(
|
|
value: _customerAccepted,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_customerAccepted = value!;
|
|
});
|
|
},
|
|
),
|
|
Flexible(
|
|
child: InkWell(
|
|
child: Text(
|
|
"Ware in ordnungsgemäßem Zustand erhalten. Aufstell- und Einbauarbeiten wurden korrekt durchgeführt",
|
|
overflow: TextOverflow.fade,
|
|
),
|
|
onTap: () {
|
|
setState(() {
|
|
_customerAccepted = !_customerAccepted;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
bottomNavigationBar: SafeArea(
|
|
top: false,
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
height: 90,
|
|
child: Center(
|
|
child: FilledButton(
|
|
onPressed: isButtonEnabled ? widget.onContinue : null,
|
|
child: const Text("Unterschreiben"),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SignaturePadStep extends StatefulWidget {
|
|
const _SignaturePadStep({
|
|
required this.controller,
|
|
required this.delivery,
|
|
required this.appBarTitle,
|
|
required this.buttonLabel,
|
|
required this.onContinue,
|
|
});
|
|
|
|
final SignatureController controller;
|
|
final Delivery delivery;
|
|
final String appBarTitle;
|
|
final String buttonLabel;
|
|
final VoidCallback onContinue;
|
|
|
|
@override
|
|
State<_SignaturePadStep> createState() => _SignaturePadStepState();
|
|
}
|
|
|
|
class _SignaturePadStepState extends State<_SignaturePadStep> {
|
|
bool _isEmpty = true;
|
|
late final VoidCallback _listener;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_isEmpty = widget.controller.isEmpty;
|
|
_listener = () {
|
|
if (_isEmpty != widget.controller.isEmpty) {
|
|
setState(() {
|
|
_isEmpty = widget.controller.isEmpty;
|
|
});
|
|
}
|
|
};
|
|
widget.controller.addListener(_listener);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.controller.removeListener(_listener);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now());
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(widget.appBarTitle)),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: ListView(
|
|
children: [
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: MediaQuery.of(context).size.height * 0.75,
|
|
child: DecoratedBox(
|
|
decoration: const BoxDecoration(color: Colors.white),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
"Lieferung an: ${widget.delivery.customer.name}",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Signature(
|
|
controller: widget.controller,
|
|
backgroundColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(),
|
|
Text(
|
|
"${widget.delivery.customer.address.city}, den $formattedDate",
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
bottomNavigationBar: SafeArea(
|
|
top: false,
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
height: 90,
|
|
child: Center(
|
|
child: FilledButton(
|
|
onPressed: _isEmpty ? null : widget.onContinue,
|
|
child: Text(widget.buttonLabel),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|