// 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); typedef OnContinue = Function( List cargoTypes, List customDuties); class CargoWidget extends StatefulWidget { final User sender; final User consignee; final List cargoTypes; final List surchargeItems; final OnPrevious? onPrevious; final OnContinue? onContinue; const CargoWidget({ super.key, required this.cargoTypes, required this.surchargeItems, this.onPrevious, this.onContinue, required this.sender, required this.consignee, }); @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'; 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() { // for cargo types if (widget.cargoTypes.isNotEmpty) { _cargoTypes = List.from(widget.cargoTypes); for (var e in _cargoTypes) { var editor = TextEditingController(); editor.text = removeTrailingZeros(e.weight); editor.addListener(inputChangeListener); cargoTypeControllers.add(editor); } onUpdated(); } 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'; editor.addListener(inputChangeListener); cargoTypeControllers.add(editor); } 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") { emptyFields.add(i); } } return emptyFields; } void onUpdated() { if (!hasValueTotalWeight && hasValueCargoes) { totalCtl.text = removeTrailingZeros(actualTotalWeight); error = null; } else { // auto populate remaining value 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) { if (emptyFieldIndexes.length == 1) { _cargoTypes.asMap().entries.forEach((e) { if (e.value.weight == 0) { e.value.weight = remainingWeight; cargoTypeControllers[e.key].text = removeTrailingZeros(e.value.weight); } }); } } } } if (mounted) { setState(() {}); } } @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; if (!cargoType.isMixCargo) { _cargoTypes.add(cargoType); } _cargoTypes.sort((a, b) => (a == b ? 0 : (a.isMixCargo ? 1 : -1))); if (cargoType.isMixCargo) { _cargoTypes.add(cargoType); } cargoTypeControllers.clear(); for (var e in _cargoTypes) { var editor = TextEditingController(); editor.text = removeTrailingZeros(e.weight); editor.addListener(inputChangeListener); cargoTypeControllers.add(editor); } if (mounted) { setState(() {}); } }), ); final cargosBox = Padding( padding: const EdgeInsets.only(top: 5), child: Wrap( alignment: WrapAlignment.spaceBetween, runSpacing: 25, children: _cargoTypes.asMap().entries.map((e) { var key = e.key; var c = e.value; return SizedBox( width: MediaQuery.of(context).size.width / 2.3, child: Column( children: [ Row( children: [ InkResponse( radius: 25, onTap: () { double totalWeight = double.tryParse(totalCtl.text) ?? 0; if (actualTotalWeight > totalWeight) { error = "Exceed total weight"; } else { double result = totalWeight - c.weight; totalCtl.text = removeTrailingZeros(result); } _cargoTypes.removeAt(key); cargoTypeControllers.removeAt(key); 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[e.key].weight = double.tryParse(value) ?? 0; onUpdated(); }, suffixIcon: InkResponse( radius: 23, onTap: () { double totalWeight = (double.tryParse(totalCtl.text) ?? 0); var list = _cargoTypes .where((e) => e.id != c.id) .toList(); double resetValue = totalWeight - (list.fold(0, (sum, value) => sum + value.weight)); setState(() { e.value.weight = resetValue; cargoTypeControllers[e.key].text = removeTrailingZeros(resetValue); }); onUpdated(); }, 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()), ) ], ), ); }).toList()), ); final totalWeightBox = Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SizedBox( width: MediaQuery.of(context).size.width / 2.3, child: Row( children: [ InkResponse( radius: 25, onTap: () { setState(() { totalCtl.clear(); }); }, child: Icon(MaterialIcons.clear, color: labelColor)), const SizedBox(width: 10), Flexible( child: inputTextFieldWidget(context, lableText: "Total", controller: totalCtl, onFieldSubmitted: (neValue) { if (hasValueCargoes) { double totalWeight = double.tryParse(neValue) ?? 0; if (totalWeight < actualTotalWeight) { setState(() { error = "Invalid total weight"; }); } else { setState(() { error = null; }); } } else { onUpdated(); } }, suffixIcon: InkResponse( radius: 23, onTap: () { setState(() { totalCtl.text = removeTrailingZeros(actualTotalWeight); 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; } widget.onContinue!(_cargoTypes, _surchareItems); } }, ); final previousBtn = PreviousButton(onTap: () { if (widget.onPrevious != null) { widget.onPrevious!(_cargoTypes, _surchareItems); } }); return Column( children: [ Expanded( child: Padding( padding: EdgeInsets.only(left: 10, right: 10), child: ListView( children: [ const SizedBox(height: 8), userRow, cargoTitle, cargosBox, const SizedBox(height: 15), Divider(), 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}) { return TextFormField( controller: controller, style: textStyle, cursorColor: primaryColor, keyboardType: TextInputType.number, onChanged: onChanged, onFieldSubmitted: onFieldSubmitted, readOnly: readOnly, decoration: InputDecoration( suffixIcon: suffixIcon, 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)), ), ); } }