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 createState() => _SignatureViewState(); } class _SignatureViewState extends State { 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().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 _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( 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), ), ), ), ), ); } }