diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index f0258e4..d21ef28 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -329,6 +329,11 @@ "box.crete.carton":"Create carton", "box.carton.type":"Carton Type", "box.select.delivery":"Select delivery type", + "box.select.package":"Select packages", + "box.no_package":"There is no packages.", + "box.input_cargo_weight":"Input cargo weight (lb)", + "box.input_surcharge_item":"Input surcharge items", + "box.select.cargo_type":"Select surcharge item", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index 722f5fb..44c0a1b 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -328,6 +328,11 @@ "box.crete.carton":"Create carton", "box.carton.type":"Carton Type", "box.select.delivery":"Select delivery type", + "box.select.package":"Select packages", + "box.no_package":"There is no packages.", + "box.input_cargo_weight":"Input cargo weight (lb)", + "box.input_surcharge_item":"Input surcharge items", + "box.select.cargo_type":"Select surcharge item", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", diff --git a/lib/app.dart b/lib/app.dart index 1ca72ad..ce6c735 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -30,6 +30,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'pages/carton/model/carton_selection_model.dart'; +import 'pages/carton/model/package_selection_model.dart'; import 'pages/delivery/model/delivery_model.dart'; class App extends StatefulWidget { @@ -64,6 +65,7 @@ class _AppState extends State { final ProcessingModel processingModel = new ProcessingModel(); final PickupModel pickupModel = new PickupModel(); final CartonSelectionModel cartonSelectionModel = new CartonSelectionModel(); + final PackageSelectionModel packageSelectionModel = new PackageSelectionModel(); late AppTranslationsDelegate _newLocaleDelegate; @@ -87,7 +89,8 @@ class _AppState extends State { ..addModel(cartonSizeModel) ..addModel(processingModel) ..addModel(pickupModel) - ..addModel(cartonSelectionModel); + ..addModel(cartonSelectionModel) + ..addModel(packageSelectionModel); _newLocaleDelegate = AppTranslationsDelegate( newLocale: Translation().supportedLocales().first); @@ -137,6 +140,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: processingModel), ChangeNotifierProvider.value(value: pickupModel), ChangeNotifierProvider.value(value: cartonSelectionModel), + ChangeNotifierProvider.value(value: packageSelectionModel), ], child: Consumer( builder: (context, value, child) { diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index ee355de..f2081a4 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -133,3 +133,7 @@ const invoice_paid_status = "paid"; const payment_pending_status = "pending"; const payment_confirmed_status = "confirmed"; const payment_canceled_status = "canceled"; + +//Delivery types +const delivery_caton = "Delivery carton"; +const pickup_carton = "Pick-up carton"; diff --git a/lib/domain/entities/package.dart b/lib/domain/entities/package.dart index 1eb7564..eb94e4f 100644 --- a/lib/domain/entities/package.dart +++ b/lib/domain/entities/package.dart @@ -14,6 +14,7 @@ class Package { DateTime? currentStatusDate; List photoUrls; List shipmentHistory; + List cartonIds; String? desc; String? status; @@ -69,7 +70,8 @@ class Package { this.deliveryAddress, this.isChecked = false, this.photoFiles = const [], - this.senderPhoneNumber}); + this.senderPhoneNumber, + this.cartonIds = const []}); factory Package.fromMap(Map map, String docID) { var _currentStatusDate = (map['status_date'] as Timestamp); @@ -82,6 +84,9 @@ class Package { var da = map['delivery_address']; var _da = da != null ? DeliveryAddress.fromMap(da, da["id"]) : null; + List cartonIds = + map['carton_ids'] == null ? [] : List.from(map['carton_ids']); + return Package( id: docID, userID: map['user_id'], @@ -99,7 +104,8 @@ class Package { deliveryAddress: _da, currentStatusDate: _currentStatusDate.toDate().toLocal(), photoUrls: _photoUrls, - shipmentHistory: _shipmentStatus); + shipmentHistory: _shipmentStatus, + cartonIds: cartonIds); } Map toJson() => { diff --git a/lib/pages/carton/cargo_widget.dart b/lib/pages/carton/cargo_widget.dart new file mode 100644 index 0000000..aabfe30 --- /dev/null +++ b/lib/pages/carton/cargo_widget.dart @@ -0,0 +1,356 @@ +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 'package:provider/provider.dart'; + +import '../../domain/entities/cargo_type.dart'; +import '../../domain/entities/user.dart'; +import '../rates/model/shipment_rate_model.dart'; +import '../widgets/continue_button.dart'; +import '../widgets/display_text.dart'; +import '../widgets/local_title.dart'; +import '../widgets/previous_button.dart'; +import 'custom_duty_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 customDuties; + final OnPrevious? onPrevious; + final OnContinue? onContinue; + + const CargoWidget({ + Key? key, + required this.cargoTypes, + required this.customDuties, + this.onPrevious, + this.onContinue, + required this.sender, + required this.consignee, + }) : super(key: key); + + @override + State createState() => _CargoWidgetState(); +} + +class _CargoWidgetState extends State { + List _cargoTypes = []; + List _customDuties = []; + TextEditingController _totalCtl = TextEditingController(); + List _cargoTypeControllers = []; + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + var model = context.read(); + _cargoTypes = model.rate.cargoTypes.map((e) => e.clone()).toList(); + + if (widget.cargoTypes.isNotEmpty) { + } else { + _cargoTypes.forEach((e) { + var editor = new TextEditingController(); + editor.text = ''; + editor.addListener(inputChangeListener); + _cargoTypeControllers.add(editor); + }); + } + + if (mounted) { + setState(() {}); + } + } + + bool isFieldEmpty(int index) { + return _cargoTypeControllers[index].text.isEmpty; + } + + List getEmptyFields() { + List emptyFields = []; + for (int i = 0; i < _cargoTypeControllers.length; i++) { + if (isFieldEmpty(i)) { + emptyFields.add(i); + } + } + return emptyFields; + } + + inputChangeListener() { + List emptyFields = getEmptyFields(); + print("emptyFields:$emptyFields"); + + // if (emptyFields.isNotEmpty && emptyFields.length == 1) { + // // _cargoTypeControllers[emptyFields.first].text = + // } + + if (emptyFields.isEmpty) { + _cargoTypes.asMap().entries.forEach((e) { + _cargoTypes[e.key].weight = + double.tryParse(_cargoTypeControllers[e.key].text) ?? 0; + }); + double total = _cargoTypes.fold(0, (sum, value) => sum + value.weight); + setState(() { + _totalCtl.text = total.toString(); + }); + } else { + // if (emptyFields.length == 1) { + // print("_totalCtl.text:${_totalCtl.text}"); + + // if (_totalCtl.text.isNotEmpty) { + // double t = double.tryParse(_totalCtl.text) ?? 0; + + // _cargoTypes.asMap().entries.forEach((e) { + // _cargoTypes[e.key].weight = + // double.tryParse(_cargoTypeControllers[e.key].text) ?? 0; + // }); + // double result = + // _cargoTypes.fold(0, (sum, value) => sum + value.weight); + + // double remaining = t - result; + // setState(() { + // _cargoTypeControllers[emptyFields.first].text = + // remaining.toString(); + // }); + // } + // } + } + } + + @override + Widget build(BuildContext context) { + final senderBox = DisplayText( + text: widget.sender.name, + labelTextKey: "box.sender.title", + iconData: MaterialCommunityIcons.account_arrow_right, + subText: Text(widget.sender.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final consigneeBox = DisplayText( + text: widget.consignee.name, + labelTextKey: "box.consignee.title", + iconData: MaterialCommunityIcons.account_arrow_left, + subText: Text(widget.consignee.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final userRow = Row( + children: [ + Expanded( + child: senderBox, + flex: 2, + ), + Flexible(child: consigneeBox) + ], + ); + + final cargosBox = Wrap( + alignment: WrapAlignment.spaceBetween, + runSpacing: 15, + 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: Row( + children: [ + InkResponse( + radius: 25, + onTap: () { + setState(() { + _cargoTypeControllers[key].clear(); + }); + }, + child: Icon(MaterialIcons.clear)), + const SizedBox(width: 10), + Flexible( + child: inputTextFieldWidget(context, + lableText: c.name ?? "", + controller: _cargoTypeControllers[key] + // onChanged: (newValue) { + // setState(() { + // _cargoTypes[key].weight = double.tryParse(newValue) ?? 0; + // }); + // double total = + // _cargoTypes.fold(0, (sum, value) => sum + value.weight); + // setState(() { + // _totalCtl.text = total.toString(); + // }); + // }, + ), + ), + ], + ), + ); + }).toList()); + + final totalBox = 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)), + const SizedBox(width: 10), + Flexible( + child: inputTextFieldWidget(context, + lableText: "Total", controller: _totalCtl), + ), + ], + )), + ], + ); + + final subchargeItemTitleBox = LocalTitle( + textKey: "box.input_surcharge_item", + trailing: IconButton( + icon: Icon( + Icons.add_circle, + color: primaryColor, + ), + onPressed: () async { + List? customList = await Navigator.push>( + context, + CupertinoPageRoute( + builder: (context) => + CustomDutyAddition(customDuties: _customDuties))); + if (customList == null) return; + + _customDuties = List.from(customList); + + if (mounted) { + setState(() {}); + } + }), + ); + + final subChargeItemsBox = Wrap( + alignment: WrapAlignment.spaceBetween, + runSpacing: 15, + children: _customDuties.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(() { + _customDuties.removeAt(key); + }); + }, + child: Icon(Feather.minus_circle)), + const SizedBox(width: 10), + Flexible( + child: inputTextFieldWidget( + context, + lableText: c.name ?? "", + ), + ), + ], + ), + ); + }).toList()); + + final continueBtn = ContinueButton( + onTap: () { + // if (selectedPackageList.isEmpty || searchResults.isEmpty) { + // showMsgDialog(context, 'Error', "Please select the packages"); + // return false; + // } + + // if (widget.onContinue != null) { + // widget.onContinue!(selectedPackageList); + // } + }, + ); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(_cargoTypes, _customDuties); + } + }); + + return Column( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10, right: 10), + child: ListView( + children: [ + const SizedBox(height: 8), + userRow, + LocalTitle(textKey: "box.input_cargo_weight", topPadding: 10), + cargosBox, + const SizedBox(height: 15), + Divider(), + const SizedBox(height: 5), + totalBox, + 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}) { + return TextFormField( + controller: controller, + style: textStyle, + cursorColor: primaryColor, + keyboardType: TextInputType.number, + onChanged: onChanged, + decoration: new InputDecoration( + 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)), + ), + ); + } +} diff --git a/lib/pages/carton/carton_cargo_table.dart b/lib/pages/carton/carton_cargo_table.dart index c6f67cf..db8fe82 100644 --- a/lib/pages/carton/carton_cargo_table.dart +++ b/lib/pages/carton/carton_cargo_table.dart @@ -90,7 +90,7 @@ class _CargoTableState extends State { ), ), DataCell( - c.isCutomDuty! + c.isCutomDuty ? GestureDetector( onTap: () async { String? _t = await showDialog( diff --git a/lib/pages/carton/carton_editor_for_package.dart b/lib/pages/carton/carton_editor_for_package.dart index 553a89f..5921cf6 100644 --- a/lib/pages/carton/carton_editor_for_package.dart +++ b/lib/pages/carton/carton_editor_for_package.dart @@ -3,25 +3,31 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import '../../../domain/constants.dart'; -import '../../../domain/entities/carton.dart'; import '../../../domain/entities/carton_size.dart'; import '../../../domain/entities/fcs_shipment.dart'; import '../../../domain/vo/local_step.dart'; import '../../../helpers/theme.dart'; +import '../../domain/entities/cargo_type.dart'; +import '../../domain/entities/package.dart'; import '../../domain/entities/user.dart'; import '../main/util.dart'; import '../widgets/local_text.dart'; import '../widgets/progress.dart'; import '../widgets/step_widget.dart'; +import 'cargo_widget.dart'; import 'carton_size_widget.dart'; -import 'mix_carton/mix_carton_submit.dart'; +import 'model/package_selection_model.dart'; +import 'package_selection_widget.dart'; class CartonEditorForPackage extends StatefulWidget { final User sender; final User consignee; - const CartonEditorForPackage({Key? key, required this.sender, required this.consignee}) : super(key: key); + const CartonEditorForPackage( + {Key? key, required this.sender, required this.consignee}) + : super(key: key); @override State createState() => _CartonEditorForPackageState(); @@ -36,18 +42,27 @@ class _CartonEditorForPackageState extends State { LocalStep(lable: 'Cargos', stepType: StepType.CARGOS), LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) ]; - List _cartions = []; + List _packages = []; + List _cargoTypes = []; + List _customDuties = []; int currentStep = 0; double _length = 0; double _width = 0; double _height = 0; + String _selectedDeliveryType = delivery_caton; FcsShipment? _shipment; String _cartonSizeType = standardCarton; CartonSize? _standardSize; bool _isLoading = false; + @override + void initState() { + context.read().clearSelection(); + super.initState(); + } + @override Widget build(BuildContext context) { return WillPopScope( @@ -113,8 +128,9 @@ class _CartonEditorForPackageState extends State { if (step.stepType == StepType.SIZE) { return Expanded( child: CartonSizeWidget( - sender: widget.sender, - consignee: widget.consignee, + deliveryType: _selectedDeliveryType, + sender: widget.sender, + consignee: widget.consignee, shipment: _shipment, cartonSizeType: _cartonSizeType, standardSize: _standardSize, @@ -124,9 +140,10 @@ class _CartonEditorForPackageState extends State { onPrevious: () { Navigator.pop(context); }, - onContinue: (shipment, cartonSizeType, + onContinue: (deliveryType, shipment, cartonSizeType, {standardSize, length, width, height}) { setState(() { + _selectedDeliveryType = deliveryType; _shipment = shipment; _cartonSizeType = cartonSizeType; _standardSize = standardSize; @@ -139,64 +156,69 @@ class _CartonEditorForPackageState extends State { )); } else if (step.stepType == StepType.PACKAGES) { return Expanded( - child: Text("PACKAGES"), - // child: CartonSelectionWidget( - // shipment: _shipment!, - // cartons: _cartions, - // onContinue: (cartons) { - // setState(() { - // _cartions = List.from(cartons); - // currentStep += 1; - // }); - // }, - // onPrevious: (cartons) { - // setState(() { - // _cartions = List.from(cartons); - // currentStep -= 1; - // }); - // }, - // ), - ); - } else if (step.stepType == StepType.CARGOS) { - return Expanded( - child: Text("cargos"), - // child: CartonSelectionWidget( - // shipment: _shipment!, - // cartons: _cartions, - // onContinue: (cartons) { - // setState(() { - // _cartions = List.from(cartons); - // currentStep += 1; - // }); - // }, - // onPrevious: (cartons) { - // setState(() { - // _cartions = List.from(cartons); - // currentStep -= 1; - // }); - // }, - // ), - ); - } else { - return Expanded( - child: MixCartonSubmit( - cartonSizeType: _cartonSizeType, - standardSize: _standardSize, - length: _length, - width: _width, - height: _height, + child: PackageSelectionWidget( + sender: widget.sender, + consignee: widget.consignee, shipment: _shipment!, - cartons: _cartions, - onCreate: () { - _create(); - }, - onPrevious: () { + packages: _packages, + onContinue: (packages) { setState(() { + _packages = List.from(packages); + currentStep += 1; + }); + }, + onPrevious: (packages) { + setState(() { + _packages = List.from(packages); currentStep -= 1; }); }, ), ); + } else if (step.stepType == StepType.CARGOS) { + return Expanded( + child: CargoWidget( + sender: widget.sender, + consignee: widget.consignee, + cargoTypes: _cargoTypes, + customDuties: _customDuties, + onContinue: (cargoTypes, customDuties) { + setState(() { + _cargoTypes = List.from(cargoTypes); + _customDuties = List.from(customDuties); + currentStep += 1; + }); + }, + onPrevious: (cargoTypes, customDuties) { + setState(() { + _cargoTypes = List.from(cargoTypes); + _customDuties = List.from(customDuties); + currentStep -= 1; + }); + }, + ), + ); + } else { + return Expanded( + child: Text("Submit"), + // child: MixCartonSubmit( + // cartonSizeType: _cartonSizeType, + // standardSize: _standardSize, + // length: _length, + // width: _width, + // height: _height, + // shipment: _shipment!, + // cartons: _packages, + // onCreate: () { + // _create(); + // }, + // onPrevious: () { + // setState(() { + // currentStep -= 1; + // }); + // }, + // ), + ); } } diff --git a/lib/pages/carton/carton_size_widget.dart b/lib/pages/carton/carton_size_widget.dart index 8f035a1..50da3fe 100644 --- a/lib/pages/carton/carton_size_widget.dart +++ b/lib/pages/carton/carton_size_widget.dart @@ -14,13 +14,14 @@ import '../widgets/box_size_picker.dart'; import '../widgets/continue_button.dart'; import '../widgets/display_text.dart'; import '../widgets/local_dropdown.dart'; +import '../widgets/local_radio_buttons.dart'; import '../widgets/local_text.dart'; import '../widgets/local_title.dart'; import '../widgets/previous_button.dart'; typedef OnPrevious = Function(); -typedef OnContinue = Function(FcsShipment shipment, String cartonSizeType, +typedef OnContinue = Function(String deliveryType,FcsShipment shipment, String cartonSizeType, {CartonSize? standardSize, double? length, double? width, double? height}); class CartonSizeWidget extends StatefulWidget { @@ -28,6 +29,7 @@ class CartonSizeWidget extends StatefulWidget { final OnContinue? onContinue; final User sender; final User consignee; + final String deliveryType; final FcsShipment? shipment; final String cartonSizeType; final CartonSize? standardSize; @@ -46,7 +48,8 @@ class CartonSizeWidget extends StatefulWidget { this.width, this.height, required this.sender, - required this.consignee}) + required this.consignee, + required this.deliveryType}) : super(key: key); @override @@ -54,11 +57,14 @@ class CartonSizeWidget extends StatefulWidget { } class _CartonSizeWidgetState extends State { + List _deliveryTypes = [delivery_caton, pickup_carton]; + FcsShipment? _shipment; String _cartionSizeType = standardCarton; List _shipments = []; CartonSize? _selectStandardSize; + late String _selectedDeliveryType; TextEditingController _widthController = new TextEditingController(); TextEditingController _heightController = new TextEditingController(); @@ -71,9 +77,10 @@ class _CartonSizeWidgetState extends State { } _init() async { + _selectedDeliveryType = widget.deliveryType; _shipment = widget.shipment; _cartionSizeType = widget.cartonSizeType; - + List cartonSizes = context.read().cartonSizes; _selectStandardSize = widget.standardSize ?? cartonSizes.first; @@ -104,17 +111,21 @@ class _CartonSizeWidgetState extends State { text: widget.sender.name, labelTextKey: "box.sender.title", iconData: MaterialCommunityIcons.account_arrow_right, + subText: Text(widget.sender.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), ); final consigneeBox = DisplayText( text: widget.consignee.name, labelTextKey: "box.consignee.title", iconData: MaterialCommunityIcons.account_arrow_left, + subText: Text(widget.consignee.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), ); final userRow = Row( children: [ - Flexible( + Expanded( child: senderBox, flex: 2, ), @@ -122,6 +133,15 @@ class _CartonSizeWidgetState extends State { ], ); + final deliveryTypeBox = LocalRadioButtons( + values: _deliveryTypes, + selectedValue: _selectedDeliveryType, + callback: (String? v) { + setState(() { + _selectedDeliveryType = v!; + }); + }); + final continueBtn = ContinueButton(onTap: () { double l = double.tryParse(_lengthController.text) ?? 0; double w = double.tryParse(_widthController.text) ?? 0; @@ -137,7 +157,7 @@ class _CartonSizeWidgetState extends State { !isCustomSize && !isNoneDefinedSize) { showMsgDialog( - context, "Error", "Please select the standard cartion size"); + context, "Error", "Please select the standard carton size"); return; } @@ -145,12 +165,12 @@ class _CartonSizeWidgetState extends State { !isStandardSize && !isNoneDefinedSize && (l == 0 || w == 0 || h == 0)) { - showMsgDialog(context, "Error", "Please add the cartion size"); + showMsgDialog(context, "Error", "Please add the carton size"); return; } if (widget.onContinue != null) { - widget.onContinue!(_shipment!, _cartionSizeType, + widget.onContinue!(_selectedDeliveryType,_shipment!, _cartionSizeType, standardSize: _selectStandardSize, length: l, width: w, height: h); } }); @@ -346,13 +366,18 @@ class _CartonSizeWidgetState extends State { children: [ const SizedBox(height: 8), userRow, + LocalTitle(textKey: "box.select.delivery", topPadding: 10), + const SizedBox(height: 5), + deliveryTypeBox, + const SizedBox(height: 5), LocalTitle(textKey: "box.select_carton_size"), const SizedBox(height: 8), cartonSizedBox, const SizedBox(height: 8), LocalTitle(textKey: "box.select_shipment"), const SizedBox(height: 5), - fcsShipmentsBox + fcsShipmentsBox, + const SizedBox(height: 30) ], )), Padding( diff --git a/lib/pages/carton/custom_duty_addition.dart b/lib/pages/carton/custom_duty_addition.dart new file mode 100644 index 0000000..d224f86 --- /dev/null +++ b/lib/pages/carton/custom_duty_addition.dart @@ -0,0 +1,127 @@ +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; +import 'package:fcs/pages/widgets/local_app_bar.dart'; +import 'package:fcs/pages/widgets/local_title.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../widgets/local_button.dart'; + +class CustomDutyAddition extends StatefulWidget { + final List customDuties; + + const CustomDutyAddition({super.key, required this.customDuties}); + @override + _CustomDutyAdditionState createState() => _CustomDutyAdditionState(); +} + +class _CustomDutyAdditionState extends State { + bool _isLoading = false; + List customDuties = []; + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + var shipmentRateModel = + Provider.of(context, listen: false); + customDuties = + shipmentRateModel.rate.customDuties.map((e) => e.clone()).toList(); + + for (var p in customDuties) { + if (widget.customDuties.any((e) => e.id == p.id)) { + p.isChecked = true; + } else { + p.isChecked = false; + } + } + + if (mounted) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + List getCargoRowList(List _c) { + return _c.map((c) { + return Container( + child: Container( + padding: + EdgeInsets.only(left: 10.0, right: 5.0, top: 3.0, bottom: 3.0), + child: InkWell( + onTap: () { + setState(() { + c.isChecked = !c.isChecked; + }); + }, + child: Row( + children: [ + Checkbox( + value: c.isChecked, + activeColor: primaryColor, + onChanged: (bool? check) { + setState(() { + c.isChecked = check ?? false; + }); + }), + new Text(c.name ?? '', style: textStyle), + ], + ), + ), + ), + ); + }).toList(); + } + + final saveBtn = Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: LocalButton( + textKey: "box.cargo.select.btn", + callBack: () { + List _cargos = + customDuties.where((c) => c.isChecked).toList(); + if (_cargos.isEmpty) { + showMsgDialog(context, 'Error', "Please select the cargo type"); + return; + } + Navigator.pop(context, _cargos); + }, + ), + ); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: LocalAppBar( + labelKey: 'box.select.cargo_type', + backgroundColor: Colors.white, + labelColor: primaryColor, + arrowColor: primaryColor), + body: Container( + padding: EdgeInsets.all(10), + child: ListView( + shrinkWrap: true, + children: [ + LocalTitle( + textKey: "box.select.cargo.title", + ), + Column( + children: getCargoRowList(customDuties), + ), + SizedBox(height: 30), + saveBtn, + SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/carton/mix_carton/mix_carton_editor.dart b/lib/pages/carton/mix_carton/mix_carton_editor.dart index 319ea2f..463ceaa 100644 --- a/lib/pages/carton/mix_carton/mix_carton_editor.dart +++ b/lib/pages/carton/mix_carton/mix_carton_editor.dart @@ -37,7 +37,7 @@ class _MixCartonEditorState extends State { LocalStep(lable: 'Cartons', stepType: StepType.CARTONS), LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) ]; - List _cartions = []; + List _cartons = []; int currentStep = 0; double _length = 0; @@ -146,16 +146,16 @@ class _MixCartonEditorState extends State { return Expanded( child: CartonSelectionWidget( shipment: _shipment!, - cartons: _cartions, + cartons: _cartons, onContinue: (cartons) { setState(() { - _cartions = List.from(cartons); + _cartons = List.from(cartons); currentStep += 1; }); }, onPrevious: (cartons) { setState(() { - _cartions = List.from(cartons); + _cartons = List.from(cartons); currentStep -= 1; }); }, @@ -170,7 +170,7 @@ class _MixCartonEditorState extends State { width: _width, height: _height, shipment: _shipment!, - cartons: _cartions, + cartons: _cartons, onCreate: () { _create(); }, diff --git a/lib/pages/carton/mix_carton/type_widget.dart b/lib/pages/carton/mix_carton/type_widget.dart index 81cfe2f..ccc2c32 100644 --- a/lib/pages/carton/mix_carton/type_widget.dart +++ b/lib/pages/carton/mix_carton/type_widget.dart @@ -109,7 +109,7 @@ class _TypeWidgetState extends State { !isCustomSize && !isNoneDefinedSize) { showMsgDialog( - context, "Error", "Please select the standard cartion size"); + context, "Error", "Please select the standard carton size"); return; } @@ -117,7 +117,7 @@ class _TypeWidgetState extends State { !isStandardSize && !isNoneDefinedSize && (l == 0 || w == 0 || h == 0)) { - showMsgDialog(context, "Error", "Please add the cartion size"); + showMsgDialog(context, "Error", "Please add the carton size"); return; } diff --git a/lib/pages/carton/model/carton_selection_model.dart b/lib/pages/carton/model/carton_selection_model.dart index b91ef7b..84e84be 100644 --- a/lib/pages/carton/model/carton_selection_model.dart +++ b/lib/pages/carton/model/carton_selection_model.dart @@ -15,7 +15,7 @@ class CartonSelectionModel extends BaseModel { bool reachEnd = false; List cartons = []; - bool isLoading= false; + bool isLoading = false; // for default carton DocumentSnapshot? _lastDocument; @@ -102,7 +102,7 @@ class CartonSelectionModel extends BaseModel { String path = "/$cartons_collection"; Query query = FirebaseFirestore.instance .collection(path) - .where("fcs_shipment_id", isEqualTo: shipmentId) + // .where("fcs_shipment_id", isEqualTo: shipmentId) // .where("status", isEqualTo: carton_processing_status) // .where("carton_type", isEqualTo: carton_mix_box) .where("is_deleted", isEqualTo: false) @@ -134,7 +134,7 @@ class CartonSelectionModel extends BaseModel { notifyListeners(); } catch (e) { log.warning("error:$e"); - }finally{ + } finally { isLoading = false; } } diff --git a/lib/pages/carton/model/package_selection_model.dart b/lib/pages/carton/model/package_selection_model.dart new file mode 100644 index 0000000..3f7e0d0 --- /dev/null +++ b/lib/pages/carton/model/package_selection_model.dart @@ -0,0 +1,170 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:logging/logging.dart'; + +import '../../../domain/constants.dart'; +import '../../../domain/entities/package.dart'; +import '../../main/model/base_model.dart'; + +class PackageSelectionModel extends BaseModel { + final log = Logger("PackageSelectionModel"); + // for search + String query = ""; + int offset = 0; + bool reachEnd = false; + List packages = []; + + bool isLoading = false; + + // for default package + DocumentSnapshot? _lastDocument; + bool ended = false; + + List selectedPackageList = []; + + Timer? t; + search(String term, + {bool imm = false, + required String shipmentId, + required String senderId, + required String consigneeId}) async { + query = term; + packages.clear(); + offset = 0; + reachEnd = false; + t?.cancel(); + t = Timer(Duration(milliseconds: imm ? 0 : 800), () async { + await loadMoreSearch( + term: term, + shipmentId: shipmentId, + consigneeId: consigneeId, + senderId: senderId); + }); + } + + Future loadMoreSearch( + {required String term, + required String shipmentId, + required String senderId, + required String consigneeId}) async { + if (term == "") { + await _refresh( + shipmentId: shipmentId, senderId: senderId, consigneeId: consigneeId); + return; + } + // int rowPerPage = 21; + // List list = []; + // SearchPara searchPara = SearchPara(filters: [], term: term); + // isLoading = true; + + // var path = + // "/search/$cartons_collection/${searchPara.escapeTerm}/$rowPerPage/$offset/${searchPara.escapeFilters}"; + + // var result = await requestAPI(path, "GET", + // token: await getToken(), url: Config.instance.searchURL); + + // if (result != null) { + // for (var row in result) { + // var item = ArtistExt.fromMapForSearch(row); + // list.add(item); + // } + // } + + // for (var p in list) { + // selectedArtistList.contains(p) + // ? p.isSelected = true + // : p.isSelected = false; + // } + + // artists.addAll(list); + // offset += rowPerPage; + // if (list.length < rowPerPage) { + // reachEnd = true; + // } + notifyListeners(); + } + + addDefaultPackages( + {required String shipmentId, + required String senderId, + required String consigneeId}) async { + packages.clear(); + await _refresh( + shipmentId: shipmentId, senderId: senderId, consigneeId: consigneeId); + } + + selectPackage(Package a) { + if (a.isChecked) { + selectedPackageList.add(a); + } else { + selectedPackageList.remove(a); + } + } + + Future _refresh( + {required String shipmentId, + required String senderId, + required String consigneeId}) async { + packages.clear(); + _lastDocument = null; + ended = false; + await loadMoreData( + shipmentId: shipmentId, senderId: senderId, consigneeId: consigneeId); + notifyListeners(); + } + + Future loadMoreData( + {required String shipmentId, + required String senderId, + required String consigneeId}) async { + int rowPerPage = 20; + + try { + isLoading = true; + String path = "/$packages_collection"; + Query query = FirebaseFirestore.instance + .collection(path) + // .where("fcs_shipment_id", isEqualTo: shipmentId) + // .where("status", isEqualTo: package_processed_status) + .where("user_id", whereIn: [senderId, consigneeId]) + .where("is_deleted", isEqualTo: false) + .orderBy("created_date", descending: true); + + if (_lastDocument != null) { + query = query.startAfterDocument(_lastDocument!); + } + + QuerySnapshot querySnap = await query.limit(rowPerPage).get(); + + if (querySnap.docs.isEmpty) return; + _lastDocument = querySnap.docs[querySnap.docs.length - 1]; + + List list = querySnap.docs.map((documentSnapshot) { + var p = Package.fromMap(documentSnapshot.data() as Map, + documentSnapshot.id); + return p; + }).toList(); + + for (var p in list) { + selectedPackageList.contains(p) + ? p.isChecked = true + : p.isChecked = false; + } + + packages.addAll(list); + if (list.length < rowPerPage) ended = true; + notifyListeners(); + } catch (e) { + log.warning("error:$e"); + } finally { + isLoading = false; + } + } + + clearSelection() { + selectedPackageList.clear(); + packages.clear(); + query = ""; + } +} diff --git a/lib/pages/carton/package_selection_result.dart b/lib/pages/carton/package_selection_result.dart new file mode 100644 index 0000000..1beba25 --- /dev/null +++ b/lib/pages/carton/package_selection_result.dart @@ -0,0 +1,120 @@ +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +import '../../../helpers/theme.dart'; +import '../../domain/entities/package.dart'; +import 'model/package_selection_model.dart'; + +typedef OnAction = Future Function(); +final NumberFormat numberFormatter = NumberFormat("#,###"); + +class PackageSelectionResult extends StatelessWidget { + final bool isLoadingMore; + final OnAction onLoadMore; + final OnAction onRefresh; + final Function(Package)? onTap; + final ScrollController controller; + + const PackageSelectionResult( + {super.key, + required this.isLoadingMore, + required this.onLoadMore, + required this.onRefresh, + this.onTap, + required this.controller}); + + bool _scrollNotification(ScrollNotification scrollInfo) { + if (!isLoadingMore && + scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { + onLoadMore(); + } + return true; + } + + @override + Widget build(BuildContext context) { + var model = context.watch(); + List searchResults = model.packages; + + return searchResults.isEmpty && !model.isLoading + ? Center( + child: LocalText(context, 'box.no_package', + color: Colors.black, fontSize: 15)) + : Column(children: [ + Expanded( + child: NotificationListener( + onNotification: _scrollNotification, + child: RefreshIndicator( + color: primaryColor, + onRefresh: () => onRefresh(), + child: ListView.builder( + controller: controller, + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) { + Package package = searchResults[index]; + + return Padding( + padding: const EdgeInsets.only(top: 5, bottom: 5), + child: InkWell( + onTap: () { + if (onTap != null) { + onTap!(package); + } + }, + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(5)), + border: + Border.all(color: Colors.grey.shade300)), + padding: EdgeInsets.only(left: 10, right: 10), + child: Row( + children: [ + Expanded( + child: new Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0), + child: new Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + new Text(package.trackingID ?? "", + style: new TextStyle( + fontSize: 15.0, + color: Colors.black)), + new Text( + package.cartonIds.isEmpty + ? "-" + : "${numberFormatter.format(package.cartonIds.length)} Boxes", + style: new TextStyle( + fontSize: 15.0, + color: Colors.grey), + ), + ], + ), + ), + ), + package.isChecked + ? Icon(Icons.check, color: primaryColor) + : const SizedBox() + ], + ), + ), + ), + ); + }, + itemCount: searchResults.length)), + )), + Container( + height: isLoadingMore ? 50.0 : 0, + color: Colors.transparent, + child: const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(primaryColor)), + )), + ]); + } +} diff --git a/lib/pages/carton/package_selection_widget.dart b/lib/pages/carton/package_selection_widget.dart new file mode 100644 index 0000000..9926920 --- /dev/null +++ b/lib/pages/carton/package_selection_widget.dart @@ -0,0 +1,289 @@ +import 'package:fcs/domain/entities/fcs_shipment.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart'; +import 'package:provider/provider.dart'; + +import '../../domain/entities/package.dart'; +import '../../domain/entities/user.dart'; +import '../main/util.dart'; +import '../widgets/barcode_scanner.dart'; +import '../widgets/continue_button.dart'; +import '../widgets/display_text.dart'; +import '../widgets/local_title.dart'; +import '../widgets/previous_button.dart'; +import 'model/carton_selection_model.dart'; +import 'model/package_selection_model.dart'; +import 'package_selection_result.dart'; + +typedef OnPrevious = Function(List packages); +typedef OnContinue = Function(List packages); + +class PackageSelectionWidget extends StatefulWidget { + final User sender; + final User consignee; + final FcsShipment shipment; + final List packages; + final OnPrevious? onPrevious; + final OnContinue? onContinue; + + const PackageSelectionWidget({ + Key? key, + required this.packages, + this.onPrevious, + this.onContinue, + required this.shipment, + required this.sender, + required this.consignee, + }) : super(key: key); + + @override + State createState() => _PackageSelectionWidgetState(); +} + +class _PackageSelectionWidgetState extends State { + final TextEditingController _controller = TextEditingController(); + String _query = ""; + bool _isLoadMore = false; + final _scrollController = ScrollController(); + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + var searchModel = context.read(); + searchModel.addDefaultPackages( + shipmentId: widget.shipment.id!, + consigneeId: widget.consignee.id!, + senderId: widget.sender.id!); + + _controller.text = searchModel.query; + _query = searchModel.query; + if (mounted) { + setState(() {}); + } + } + + @override + void didUpdateWidget(covariant PackageSelectionWidget oldWidget) { + _init(); + super.didUpdateWidget(oldWidget); + } + + Future _loadMoreData() async { + if (_isLoadMore) return; + var model = context.read(); + if (model.reachEnd || model.ended) return; + setState(() { + _isLoadMore = true; + }); + if (_query != "") { + await model.loadMoreSearch( + term: _query, + shipmentId: widget.shipment.id!, + consigneeId: widget.consignee.id!, + senderId: widget.sender.id!); + } else { + await model.loadMoreData( + shipmentId: widget.shipment.id!, + consigneeId: widget.consignee.id!, + senderId: widget.sender.id!); + } + + setState(() { + _isLoadMore = false; + }); + } + + @override + Widget build(BuildContext context) { + var model = context.watch(); + List searchResults = model.packages; + List selectedPackageList = model.selectedPackageList; + + final senderBox = DisplayText( + text: widget.sender.name, + labelTextKey: "box.sender.title", + iconData: MaterialCommunityIcons.account_arrow_right, + subText: Text(widget.sender.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final consigneeBox = DisplayText( + text: widget.consignee.name, + labelTextKey: "box.consignee.title", + iconData: MaterialCommunityIcons.account_arrow_left, + subText: Text(widget.consignee.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final userRow = Row( + children: [ + Expanded( + child: senderBox, + flex: 2, + ), + Flexible(child: consigneeBox) + ], + ); + + final continueBtn = ContinueButton( + onTap: () { + if (selectedPackageList.isEmpty || searchResults.isEmpty) { + showMsgDialog(context, 'Error', "Please select the packages"); + return false; + } + + if (widget.onContinue != null) { + widget.onContinue!(selectedPackageList); + } + }, + ); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(selectedPackageList); + } + }); + + final searchBox = SizedBox( + height: 40, + child: TextField( + controller: _controller, + cursorColor: primaryColor, + onSubmitted: (value) { + setState(() { + _query = value; + }); + _search(imm: true); + }, + onChanged: (v) { + setState(() { + _query = v; + }); + _search(); + }, + decoration: InputDecoration( + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide(color: labelColor), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.0), + borderSide: + BorderSide(color: labelColor.withOpacity(0.3), width: 0)), + focusedBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + borderSide: BorderSide(color: labelColor), + ), + hintText: "Search by tracking number", + hintStyle: const TextStyle( + color: Colors.grey, + fontSize: 16, + fontWeight: FontWeight.normal), + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkResponse( + radius: 20, + onTap: () { + _scan(context); + }, + child: const Icon(Icons.qr_code_scanner, + color: Colors.black87)), + IconButton( + splashRadius: 20, + onPressed: () { + setState(() { + _controller.clear(); + _query = ""; + }); + _search(); + }, + icon: const Icon(Icons.close, color: Colors.black87)), + ], + ), + contentPadding: const EdgeInsets.all(10), + filled: true), + ), + ); + + return Column( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10, right: 10), + child: Column( + children: [ + const SizedBox(height: 8), + userRow, + LocalTitle(textKey: "box.select.package", topPadding: 10), + const SizedBox(height: 10), + searchBox, + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 10), + child: PackageSelectionResult( + controller: _scrollController, + isLoadingMore: _isLoadMore, + onLoadMore: _loadMoreData, + onRefresh: () async { + _init(); + }, + onTap: (a) async { + setState(() { + a.isChecked = !a.isChecked; + }); + context.read().selectPackage(a); + }, + ), + ), + ), + ], + ), + ), + ), + 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) + ], + ); + } + + _scan(BuildContext context) async { + try { + String? barcode = await scanBarcode(); + if (barcode != null) { + setState(() { + _controller.text = barcode; + _query = barcode; + }); + await _search(); + } + } catch (e) { + showMsgDialog(context, 'Error', e.toString()); + } + } + + _search({bool imm = false}) async { + try { + await context + .read() + .search(_query, imm: imm, shipmentId: widget.shipment.id!); + } catch (e) { + showMsgDialog(context, 'Error', e.toString()); + } + } +} diff --git a/lib/pages/widgets/local_title.dart b/lib/pages/widgets/local_title.dart index 2c3ce3c..0a52481 100644 --- a/lib/pages/widgets/local_title.dart +++ b/lib/pages/widgets/local_title.dart @@ -5,10 +5,11 @@ import 'package:flutter/material.dart'; class LocalTitle extends StatelessWidget { final String? textKey; final Widget? trailing; + final double topPadding; final List? translationVariables; const LocalTitle( - {Key? key, this.textKey, this.trailing, this.translationVariables}) + {Key? key, this.textKey, this.trailing, this.translationVariables,this.topPadding = 18}) : super(key: key); @override @@ -17,7 +18,7 @@ class LocalTitle extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.only(top: 18), + padding: EdgeInsets.only(top: topPadding), child: Row( children: [ LocalText(context, textKey!,