// ignore_for_file: unused_local_variable import 'package:fcs/helpers/theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart'; import '../../domain/entities/cargo_type.dart'; import '../../domain/entities/user.dart'; import '../main/util.dart'; import '../widgets/continue_button.dart'; import '../widgets/local_title.dart'; import '../widgets/previous_button.dart'; import 'cargo_type_addition.dart'; import 'cargo_type_addition_dialog.dart'; import 'mix_cargo_type_addition_dialog.dart'; import 'surcharge_item_addition.dart'; typedef OnPrevious = Function(List cargoTypes, List customDuties, double totalWeight); typedef OnContinue = Function(List cargoTypes, List customDuties, double totalWeight); class CargoWidget extends StatefulWidget { final User sender; final User consignee; final List cargoTypes; final List surchargeItems; final OnPrevious? onPrevious; final OnContinue? onContinue; final double totalWeight; const CargoWidget( {super.key, required this.cargoTypes, required this.surchargeItems, this.onPrevious, this.onContinue, required this.sender, required this.consignee, this.totalWeight = 0}); @override State createState() => _CargoWidgetState(); } class _CargoWidgetState extends State { List _cargoTypes = []; List _surchareItems = []; TextEditingController totalCtl = TextEditingController(); List cargoTypeControllers = []; List surchargeControllers = []; bool get hasValueTotalWeight => totalCtl.text.isNotEmpty && totalCtl.text != '0.00'; bool get hasValueCargoes => _cargoTypes.isNotEmpty && _cargoTypes.every((e) => e.weight != 0); double get actualTotalWeight => _cargoTypes.fold(0, (sum, value) => sum + value.weight); String? error; @override void initState() { _init(); super.initState(); } _init() { totalCtl.clear(); // for cargo types if (widget.cargoTypes.isNotEmpty) { _cargoTypes = List.from(widget.cargoTypes); for (var e in _cargoTypes) { var editor = TextEditingController(); editor.text = twoDecimalFormatted( double.tryParse(removeTrailingZeros(e.weight)) ?? 0); editor.addListener(inputChangeListener); cargoTypeControllers.add(editor); } totalCtl.text = twoDecimalFormatted( double.tryParse(removeTrailingZeros(widget.totalWeight)) ?? 0); _onPopulate(); } else { WidgetsBinding.instance.addPostFrameCallback((_) { _openCargoTypeSelection(); }); } //for surcharge items if (widget.surchargeItems.isNotEmpty) { _surchareItems = List.from(widget.surchargeItems); for (var e in _surchareItems) { var editor = TextEditingController(); editor.text = e.qty.toString(); editor.addListener(inputChangeListener); surchargeControllers.add(editor); } } if (mounted) { setState(() {}); } } _openCargoTypeSelection() async { List? cargoes = await showDialog( context: context, builder: (_) => const CargoTypeAdditionDialog()); if (cargoes == null) return; _cargoTypes = cargoes; _cargoTypes.sort((a, b) => (a == b ? 0 : (a.isMixCargo ? 1 : -1))); for (var e in _cargoTypes) { var editor = TextEditingController(); editor.text = '0.00'; editor.addListener(inputChangeListener); cargoTypeControllers.add(editor); } totalCtl.text = twoDecimalFormatted( double.tryParse(removeTrailingZeros(actualTotalWeight)) ?? 0); if (mounted) { setState(() {}); } } inputChangeListener() { setState(() {}); } List getEmptyFields() { List emptyFields = []; for (int i = 0; i < cargoTypeControllers.length; i++) { if (cargoTypeControllers[i].text.trim().isEmpty || cargoTypeControllers[i].text.trim() == "0.00") { emptyFields.add(i); } } return emptyFields; } void _onPopulate() { if (!hasValueTotalWeight && hasValueCargoes) { totalCtl.text = twoDecimalFormatted( double.tryParse(removeTrailingZeros(actualTotalWeight)) ?? 0); error = null; } else { double totalWeight = (double.tryParse(totalCtl.text) ?? 0); if (actualTotalWeight > totalWeight) { error = "Exceed total weight"; } else { error = null; double remainingWeight = (totalWeight - actualTotalWeight).clamp(0, totalWeight); List emptyFieldIndexes = getEmptyFields(); if (emptyFieldIndexes.isNotEmpty) { // auto populate remaining value if (emptyFieldIndexes.length == 1) { _cargoTypes.asMap().entries.forEach((e) { if (e.value.weight == 0) { e.value.weight = remainingWeight; cargoTypeControllers[e.key].text = twoDecimalFormatted( double.tryParse(removeTrailingZeros(e.value.weight)) ?? 0); } }); } else { _onCheckTotalWeight(twoDecimalFormatted(totalWeight)); } } else { _onCheckTotalWeight(twoDecimalFormatted(totalWeight)); } } } if (mounted) { setState(() {}); } } void _onCheckTotalWeight(String value) { double totalWeight = double.tryParse(value) ?? 0; if (totalWeight != actualTotalWeight) { error = "Invalid total weight"; } else { error = null; } if (mounted) { setState(() {}); } } @override void dispose() { for (var controller in cargoTypeControllers) { controller.dispose(); } for (var controller in surchargeControllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { final senderBox = userDisplayBox(context, lableKey: "box.sender.title", icon: MaterialCommunityIcons.account_arrow_right, showLink: false, name: widget.sender.name ?? "", fcsID: widget.sender.fcsID ?? ""); final consigneeBox = userDisplayBox(context, showLink: false, lableKey: "box.consignee.title", icon: MaterialCommunityIcons.account_arrow_left, name: widget.consignee.name, fcsID: widget.consignee.fcsID); final userRow = Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 2, child: consigneeBox), Flexible(child: senderBox) ], ); final cargoTitle = LocalTitle( textKey: "box.input_cargo_weight", topPadding: 0, trailing: IconButton( icon: Icon( Icons.add_circle, color: primaryColor, ), onPressed: () async { CargoType? cargoType = await Navigator.push( context, CupertinoPageRoute( builder: (context) => CargoTypeAddition(cargoTypes: _cargoTypes))); if (cargoType == null) return; // add cargo type if (cargoType.isMixCargo) { int lastTrueIndex = _cargoTypes.lastIndexWhere((e) => e.isMixCargo); if (lastTrueIndex != -1) { _cargoTypes.insert(lastTrueIndex + 1, cargoType); } else { _cargoTypes.add(cargoType); } } else { int lastFalseIndex = _cargoTypes.lastIndexWhere((e) => !e.isMixCargo); if (lastFalseIndex != -1) { _cargoTypes.insert(lastFalseIndex + 1, cargoType); } else { _cargoTypes.insert(0, cargoType); } } cargoTypeControllers.clear(); for (var e in _cargoTypes) { var editor = TextEditingController(); editor.text = twoDecimalFormatted( double.tryParse(removeTrailingZeros(e.weight)) ?? 0); editor.addListener(inputChangeListener); cargoTypeControllers.add(editor); } if (mounted) { setState(() {}); } }), ); final totalWeightBox = Padding( padding: const EdgeInsets.only(top: 5), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SizedBox( width: MediaQuery.of(context).size.width / 2.3, child: Row( children: [ InkResponse( radius: 25, onTap: () { setState(() { totalCtl.clear(); }); _onCheckTotalWeight(totalCtl.text); }, child: Icon(MaterialIcons.clear, color: labelColor)), const SizedBox(width: 10), Flexible( child: inputTextFieldWidget(context, lableText: "Total", controller: totalCtl, onFieldSubmitted: (newValue) { _onCheckTotalWeight(newValue); }, suffixIcon: InkResponse( radius: 23, onTap: () { setState(() { totalCtl.text = twoDecimalFormatted( double.tryParse(removeTrailingZeros( actualTotalWeight)) ?? 0); error = null; }); }, child: Icon(Ionicons.md_refresh_circle, color: labelColor, size: 22))), ), ], )), ], ), ); final subchargeItemTitleBox = LocalTitle( textKey: "box.input_surcharge_item", trailing: IconButton( icon: Icon( Icons.add_circle, color: primaryColor, ), onPressed: () async { CargoType? surchargeItem = await Navigator.push( context, CupertinoPageRoute( builder: (context) => SurchargeItemAddition(items: _surchareItems))); if (surchargeItem == null) return; _surchareItems.add(surchargeItem); surchargeControllers.clear(); _surchareItems.asMap().entries.forEach((e) { var editor = TextEditingController(); editor.text = e.value.qty.toString(); surchargeControllers.add(editor); }); if (mounted) { setState(() {}); } }), ); final subChargeItemsBox = Padding( padding: const EdgeInsets.only(top: 5), child: Wrap( alignment: WrapAlignment.spaceBetween, runSpacing: 25, children: _surchareItems.asMap().entries.map((e) { var key = e.key; var c = e.value; return SizedBox( width: MediaQuery.of(context).size.width / 2.3, child: Row( children: [ InkResponse( radius: 25, onTap: () { setState(() { _surchareItems.removeAt(key); }); }, child: Icon(Feather.minus_circle, color: labelColor)), const SizedBox(width: 10), Flexible( child: inputTextFieldWidget( context, lableText: c.name ?? "", controller: surchargeControllers[key], onChanged: (newValue) { setState(() { _surchareItems[key].qty = int.tryParse(newValue) ?? 0; }); }, ), ), ], ), ); }).toList()), ); final continueBtn = ContinueButton( onTap: () { if (widget.onContinue != null) { if (_surchareItems.isNotEmpty && _surchareItems.any((item) => item.qty == 0)) { showMsgDialog( context, "Error", "Please insert surcharge item quantity"); return; } if (error != null) { showMsgDialog( context, "Error", "Please add the right cargo type weight"); return; } widget.onContinue!( _cargoTypes, _surchareItems, double.tryParse(totalCtl.text) ?? 0); } }, ); final previousBtn = PreviousButton(onTap: () { if (widget.onPrevious != null) { widget.onPrevious!( _cargoTypes, _surchareItems, double.tryParse(totalCtl.text) ?? 0); } }); Widget cargoesWidget(List items) { List widgets = []; for (int i = 0; i < items.length; i++) { var key = i; var c = items[i]; if (i > 0 && (!items[i - 1].isMixCargo && items[i].isMixCargo)) { widgets.add(Padding( padding: const EdgeInsets.symmetric(horizontal: 70), child: Divider(color: Colors.grey.shade300))); } widgets.add(SizedBox( width: MediaQuery.of(context).size.width / 2.3, child: Column( children: [ Row( children: [ InkResponse( radius: 25, onTap: () { _cargoTypes.removeAt(key); cargoTypeControllers.removeAt(key); _onCheckTotalWeight(totalCtl.text); if (mounted) { setState(() {}); } }, child: Icon(Feather.minus_circle, color: labelColor)), const SizedBox(width: 10), Flexible( child: inputTextFieldWidget(context, lableText: c.name ?? "", controller: cargoTypeControllers[key], onFieldSubmitted: (value) { _cargoTypes[key].weight = double.tryParse(value) ?? 0; _onPopulate(); }, suffixIcon: InkResponse( radius: 23, onTap: () { double totalWeight = (double.tryParse(totalCtl.text) ?? 0); var list = _cargoTypes .where((e) => e.id != c.id) .toList(); double sum = (list.fold( 0, (sum, value) => sum + value.weight)); if (sum > totalWeight) { error = "Exceed total weight"; } else { error = null; double resetValue = totalWeight - sum; setState(() { c.weight = resetValue; cargoTypeControllers[key].text = twoDecimalFormatted(double.tryParse( removeTrailingZeros( resetValue)) ?? 0); }); _onPopulate(); } }, child: Icon(Ionicons.md_refresh_circle, color: labelColor, size: 22))), ), c.isMixCargo ? InkResponse( radius: 23, onTap: () async { List? cargoes = await showDialog( context: context, builder: (_) => MixCargoTypeAdditionDialog( cargoTypes: c.mixCargoes)); if (cargoes == null) return; setState(() { c.mixCargoes = List.from(cargoes); }); }, child: Icon(Icons.add_circle, color: labelColor, size: 22)) : const SizedBox() ], ), c.mixCargoes.isEmpty ? const SizedBox() : Padding( padding: const EdgeInsets.only(top: 5), child: Column( children: c.mixCargoes.map((e) { return Padding( padding: const EdgeInsets.only(top: 12), child: Row( children: [ const SizedBox(width: 25), InkResponse( radius: 23, onTap: () { setState(() { c.mixCargoes.remove(e); }); }, child: Icon(Feather.minus_circle, color: labelColor, size: 20)), Padding( padding: const EdgeInsets.only(left: 10), child: Text(e.name ?? ""), ), ], ), ); }).toList()), ) ], ), )); } return Padding( padding: const EdgeInsets.only(top: 5), child: Wrap( alignment: WrapAlignment.spaceBetween, runSpacing: 25, children: widgets), ); } return Column( children: [ Expanded( child: Padding( padding: EdgeInsets.only(left: 10, right: 10), child: ListView( children: [ const SizedBox(height: 8), userRow, cargoTitle, cargoesWidget(_cargoTypes), const SizedBox(height: 15), _cargoTypes.isNotEmpty ? Divider( color: Colors.grey.shade300, ) : const SizedBox(), const SizedBox(height: 5), error != null ? Text( error!, style: TextStyle(color: dangerColor), ) : const SizedBox(), totalWeightBox, subchargeItemTitleBox, subChargeItemsBox, const SizedBox(height: 30), ], ), ), ), widget.onContinue != null ? Padding( padding: const EdgeInsets.only(left: 15, right: 15, top: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ previousBtn, continueBtn, ], ), ) : const SizedBox(), const SizedBox(height: 20) ], ); } Widget inputTextFieldWidget(BuildContext context, {required String lableText, TextEditingController? controller, Function(String)? onChanged, Function(String)? onFieldSubmitted, bool readOnly = false, Widget? suffixIcon, FocusNode? focusNode}) { return TextFormField( controller: controller, focusNode: focusNode, style: textStyle, cursorColor: primaryColor, keyboardType: TextInputType.number, onChanged: onChanged, onFieldSubmitted: onFieldSubmitted, readOnly: readOnly, decoration: InputDecoration( suffixIcon: Padding( padding: const EdgeInsets.only(right: 8), child: suffixIcon, ), suffixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0), contentPadding: EdgeInsets.all(0), labelText: lableText, labelStyle: newLabelStyle(color: Colors.black54, fontSize: 17), enabledBorder: UnderlineInputBorder( borderSide: BorderSide(color: primaryColor, width: 1.0)), focusedBorder: UnderlineInputBorder( borderSide: BorderSide(color: primaryColor, width: 1.0)), disabledBorder: UnderlineInputBorder( borderSide: BorderSide(color: primaryColor, width: 1.0)), ), ); } }