From 891330a59e5930f7b54f6a1dee80e2737e41ae84 Mon Sep 17 00:00:00 2001 From: tzw Date: Fri, 2 Feb 2024 18:00:51 +0630 Subject: [PATCH] add carton editor for mix carton --- assets/local/localization_en.json | 2 + assets/local/localization_mu.json | 2 + lib/app.dart | 6 +- lib/domain/constants.dart | 5 + lib/domain/entities/carton.dart | 4 +- lib/domain/entities/fcs_shipment.dart | 6 + lib/pages/carton/carton_editor.dart | 32 +-- .../mix_cation/carton_selection_result.dart | 116 ++++++++ .../mix_cation/carton_selection_widget.dart | 246 +++++++++++++++++ .../carton/mix_cation/mix_cartion_editor.dart | 101 +++---- lib/pages/carton/mix_cation/type_widget.dart | 248 ++++++++++++------ .../carton/model/carton_selection_model.dart | 142 ++++++++++ lib/pages/widgets/box_size_picker.dart | 47 ++++ lib/pages/widgets/display_text.dart | 7 +- lib/pages/widgets/input_text.dart | 31 ++- lib/pages/widgets/local_dropdown.dart | 14 +- lib/pages/widgets/local_radio.dart | 33 +++ lib/pages/widgets/local_radio_buttons.dart | 19 +- 18 files changed, 903 insertions(+), 158 deletions(-) create mode 100644 lib/pages/carton/mix_cation/carton_selection_result.dart create mode 100644 lib/pages/carton/mix_cation/carton_selection_widget.dart create mode 100644 lib/pages/carton/model/carton_selection_model.dart create mode 100644 lib/pages/widgets/box_size_picker.dart create mode 100644 lib/pages/widgets/local_radio.dart diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 048fa1e..f689b7c 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -322,6 +322,8 @@ "box.standard_carton_size":"Standard carton size", "box.custom_size":"Custom size", "box.package_size":"Package", + "box.select.cartion":"Select cartons", + "box.no_carton":"There is no cartons in this shipment.", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index c239fa6..8fa992a 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -321,6 +321,8 @@ "box.standard_carton_size":"Standard carton size", "box.custom_size":"Custom size", "box.package_size":"Package", + "box.select.cartion":"Select cartons", + "box.no_carton":"There is no cartons in this shipment.", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", diff --git a/lib/app.dart b/lib/app.dart index 0eed74d..1ca72ad 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -29,6 +29,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; +import 'pages/carton/model/carton_selection_model.dart'; import 'pages/delivery/model/delivery_model.dart'; class App extends StatefulWidget { @@ -62,6 +63,7 @@ class _AppState extends State { final CartonSizeModel cartonSizeModel = new CartonSizeModel(); final ProcessingModel processingModel = new ProcessingModel(); final PickupModel pickupModel = new PickupModel(); + final CartonSelectionModel cartonSelectionModel = new CartonSelectionModel(); late AppTranslationsDelegate _newLocaleDelegate; @@ -84,7 +86,8 @@ class _AppState extends State { ..addModel(deliveryModel) ..addModel(cartonSizeModel) ..addModel(processingModel) - ..addModel(pickupModel); + ..addModel(pickupModel) + ..addModel(cartonSelectionModel); _newLocaleDelegate = AppTranslationsDelegate( newLocale: Translation().supportedLocales().first); @@ -133,6 +136,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: cartonSizeModel), ChangeNotifierProvider.value(value: processingModel), ChangeNotifierProvider.value(value: pickupModel), + ChangeNotifierProvider.value(value: cartonSelectionModel), ], child: Consumer( builder: (context, value, child) { diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index cb8b980..ee355de 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -95,6 +95,11 @@ const carton_from_shipments = "From shipments"; const carton_small_bag = "Small bag"; const carton_mix_box = "Mix box"; +// carton Size +const standardCarton = "Standard carton size"; +const customCarton = "Custom size"; +const packageCartion = "Package"; + //Mix types const mix_delivery = "Mix Delivery"; const mix_pickup = "Mix Pickup"; diff --git a/lib/domain/entities/carton.dart b/lib/domain/entities/carton.dart index fdbac78..aefc255 100644 --- a/lib/domain/entities/carton.dart +++ b/lib/domain/entities/carton.dart @@ -37,6 +37,7 @@ class Carton { String? cartonSizeID; String? cartonSizeName; double cartonWeight; + bool isSelected; int rate; int weight; @@ -158,7 +159,8 @@ class Carton { this.mixBoxType, this.mixCartons = const [], this.mixCartonIDs = const [], - this.cartonWeight =0}); + this.cartonWeight =0, + this.isSelected = false}); Map toMap() { List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList(); diff --git a/lib/domain/entities/fcs_shipment.dart b/lib/domain/entities/fcs_shipment.dart index 933be02..b7ca8b9 100644 --- a/lib/domain/entities/fcs_shipment.dart +++ b/lib/domain/entities/fcs_shipment.dart @@ -80,4 +80,10 @@ class FcsShipment { fcsShipment.port != this.port || fcsShipment.destination != this.destination; } + + @override + bool operator ==(Object other) => other is FcsShipment && other.id == id; + + @override + int get hashCode => id.hashCode; } diff --git a/lib/pages/carton/carton_editor.dart b/lib/pages/carton/carton_editor.dart index 6d18031..8998c6e 100644 --- a/lib/pages/carton/carton_editor.dart +++ b/lib/pages/carton/carton_editor.dart @@ -6,6 +6,7 @@ import 'package:fcs/pages/user_search/user_serach.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/fcs_id_icon.dart'; import 'package:fcs/pages/widgets/local_app_bar.dart'; +import 'package:fcs/pages/widgets/local_radio.dart'; import 'package:fcs/pages/widgets/local_radio_buttons.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_title.dart'; @@ -124,11 +125,10 @@ class _CartonEditorState extends State { onTap: () { //for packages if (isFromPackages) { - } // for mix cartion else { - Navigator.push( + Navigator.push( context, CupertinoPageRoute( builder: (context) => const MixCartonEditor())); @@ -244,14 +244,10 @@ class _CartonEditorState extends State { }); }, child: Row(children: [ - Radio( - visualDensity: const VisualDensity( - horizontal: VisualDensity.minimumDensity), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - activeColor: primaryColor, - groupValue: _billToValue, + LocalRadio( value: 1, - onChanged: (value) { + groupValue: _billToValue, + onChanged: (p0) { setState(() { _billToValue = 1; }); @@ -261,7 +257,7 @@ class _CartonEditorState extends State { child: Padding( padding: const EdgeInsets.only(left: 10), child: LocalText(context, 'box.bill_to_sender', - fontSize: 14, + fontSize: 15, color: _billToValue == 1 ? primaryColor : Colors.black), ), ) @@ -275,14 +271,10 @@ class _CartonEditorState extends State { }); }, child: Row(children: [ - Radio( - visualDensity: const VisualDensity( - horizontal: VisualDensity.minimumDensity), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - activeColor: primaryColor, - groupValue: _billToValue, + LocalRadio( value: 2, - onChanged: (value) { + groupValue: _billToValue, + onChanged: (p0) { setState(() { _billToValue = 2; }); @@ -292,7 +284,7 @@ class _CartonEditorState extends State { child: Padding( padding: const EdgeInsets.only(left: 10), child: LocalText(context, 'box.bill_to.consignee', - fontSize: 14, + fontSize: 15, color: _billToValue == 2 ? primaryColor : Colors.black), ), ) @@ -327,7 +319,9 @@ class _CartonEditorState extends State { Flexible(child: consigneeBox) ], ), - billRadioBox + const SizedBox(height: 5), + billRadioBox, + const SizedBox(height: 5), ], ) : Container(), diff --git a/lib/pages/carton/mix_cation/carton_selection_result.dart b/lib/pages/carton/mix_cation/carton_selection_result.dart new file mode 100644 index 0000000..356e4fa --- /dev/null +++ b/lib/pages/carton/mix_cation/carton_selection_result.dart @@ -0,0 +1,116 @@ +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../../domain/entities/carton.dart'; +import '../../../helpers/theme.dart'; +import '../model/carton_selection_model.dart'; + +typedef OnAction = Future Function(); + +class CartonSelectionResult extends StatelessWidget { + final bool isLoadingMore; + final OnAction onLoadMore; + final OnAction onRefresh; + final Function(Carton)? onTap; + final ScrollController controller; + + const CartonSelectionResult( + {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.cartons; + + return searchResults.isEmpty + ? Center( + child: LocalText(context, 'box.no_carton', + 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) { + Carton carton = searchResults[index]; + + return Padding( + padding: const EdgeInsets.only(top: 5, bottom: 5), + child: InkWell( + onTap: () { + if (onTap != null) { + onTap!(carton); + } + }, + 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(carton.cartonNumber ?? "", + style: new TextStyle( + fontSize: 15.0, + color: Colors.black)), + new Text( + "${carton.cartonWeight.toStringAsFixed(2)} lb", + style: new TextStyle( + fontSize: 15.0, + color: Colors.grey), + ), + ], + ), + ), + ), + carton.isSelected + ? 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/mix_cation/carton_selection_widget.dart b/lib/pages/carton/mix_cation/carton_selection_widget.dart new file mode 100644 index 0000000..1e71644 --- /dev/null +++ b/lib/pages/carton/mix_cation/carton_selection_widget.dart @@ -0,0 +1,246 @@ +import 'package:fcs/domain/entities/fcs_shipment.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/carton/mix_cation/carton_selection_result.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../../domain/entities/carton.dart'; +import '../../main/util.dart'; +import '../../widgets/barcode_scanner.dart'; +import '../../widgets/continue_button.dart'; +import '../../widgets/local_title.dart'; +import '../../widgets/previous_button.dart'; +import '../model/carton_selection_model.dart'; + +typedef OnPrevious = Function(List cartons); +typedef OnContinue = Function(List cartons); + +class CartonSelectionWidget extends StatefulWidget { + final FcsShipment shipment; + final List cartons; + + final OnPrevious? onPrevious; + final OnContinue? onContinue; + + const CartonSelectionWidget({ + Key? key, + required this.cartons, + this.onPrevious, + this.onContinue, + required this.shipment, + }) : super(key: key); + + @override + State createState() => _CartonSelectionWidgetState(); +} + +class _CartonSelectionWidgetState 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.addDefaultCartons(widget.shipment.id!); + + _controller.text = searchModel.query; + _query = searchModel.query; + if (mounted) { + setState(() {}); + } + } + + @override + void didUpdateWidget(covariant CartonSelectionWidget 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!); + } else { + await model.loadMoreData(widget.shipment.id!); + } + + setState(() { + _isLoadMore = false; + }); + } + + @override + Widget build(BuildContext context) { + var model = context.watch(); + List searchResults = model.cartons; + List selectedCartonList = model.selectedCartonList; + + final continueBtn = ContinueButton( + onTap: () { + + if (selectedCartonList.isEmpty || searchResults.isEmpty) { + showMsgDialog(context, 'Error', "Please select the cartons"); + return false; + } + + if (widget.onContinue != null) { + widget.onContinue!(selectedCartonList); + } + }, + ); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(selectedCartonList); + } + }); + + 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 carton 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), + LocalTitle(textKey: "box.select.cartion"), + const SizedBox(height: 10), + searchBox, + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 10), + child: CartonSelectionResult( + controller: _scrollController, + isLoadingMore: _isLoadMore, + onLoadMore: _loadMoreData, + onRefresh: () async { + _init(); + }, + onTap: (a) async { + setState(() { + a.isSelected = !a.isSelected; + }); + context.read().selectCarton(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/carton/mix_cation/mix_cartion_editor.dart b/lib/pages/carton/mix_cation/mix_cartion_editor.dart index a9eff22..ce3b03d 100644 --- a/lib/pages/carton/mix_cation/mix_cartion_editor.dart +++ b/lib/pages/carton/mix_cation/mix_cartion_editor.dart @@ -1,12 +1,21 @@ +// ignore_for_file: deprecated_member_use + 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 '../../widgets/local_text.dart'; import '../../widgets/progress.dart'; import '../../widgets/step_widget.dart'; +import '../model/carton_selection_model.dart'; +import 'carton_selection_widget.dart'; import 'type_widget.dart'; class MixCartonEditor extends StatefulWidget { @@ -26,12 +35,21 @@ class _MixCartonEditorState extends State { LocalStep(lable: 'Cartons', stepType: StepType.CARTONS), LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) ]; + List _cartions = []; - bool _isLoading = false; int currentStep = 0; + double _length = 0; + double _width = 0; + double _height = 0; + + FcsShipment? _shipment; + String _cartionSizeType = standardCarton; + CartonSize? _standardSize; + bool _isLoading = false; @override void initState() { + context.read().clearSelection(); super.initState(); } @@ -98,57 +116,48 @@ class _MixCartonEditorState extends State { Widget getContent(int index) { var step = steps[index]; if (step.stepType == StepType.TYPE) { - return Expanded(child: TypeWidget( + return Expanded( + child: TypeWidget( + shipment: _shipment, + cartonSizeType: _cartionSizeType, + standardSize: _standardSize, + length: _length, + width: _width, + height: _height, onPrevious: () { Navigator.pop(context); }, - // warehouse: _warehouse, - // onSelectWarehouse: (w) { - // setState(() { - // _warehouse = w; - // currentStep += 1; - // }); - // }, + onContinue: (shipment, cartonSizeType, + {standardSize, length, width, height}) { + setState(() { + _shipment = shipment; + _cartionSizeType = cartonSizeType; + _standardSize = standardSize; + _length = length ?? 0; + _width = width ?? 0; + _height = height ?? 0; + currentStep += 1; + }); + }, )); } else if (step.stepType == StepType.CARTONS) { return Expanded( - child: Text("cartons"), - // child: StockAdjustmentProducts( - // products: products, - // onAdd: (ps) { - // setState(() { - // products = List.from(ps); - // }); - // }, - // onRemove: (p) { - // setState(() { - // products.removeWhere((e) => e.id == p.id); - // }); - // }, - // onRemoveAll: (ps) { - // for (var e in ps) { - // setState(() { - // products.removeWhere((p) => p.id == e.id); - // }); - // } - // }, - // onContinue: (ps) { - // if (products.isEmpty) { - // showMsgDialog(context, 'Error', "Please select product"); - // return false; - // } - // setState(() { - // products = List.from(ps); - // currentStep += 1; - // }); - // }, - // onPrevious: (ps) { - // setState(() { - // products = List.from(ps); - // currentStep -= 1; - // }); - // }, - // ), + 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( diff --git a/lib/pages/carton/mix_cation/type_widget.dart b/lib/pages/carton/mix_cation/type_widget.dart index 4095ddc..81cfe2f 100644 --- a/lib/pages/carton/mix_cation/type_widget.dart +++ b/lib/pages/carton/mix_cation/type_widget.dart @@ -1,10 +1,15 @@ +import 'package:fcs/pages/widgets/local_radio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:provider/provider.dart'; +import '../../../domain/constants.dart'; +import '../../../domain/entities/carton_size.dart'; import '../../../domain/entities/fcs_shipment.dart'; import '../../../helpers/theme.dart'; +import '../../carton_size/model/carton_size_model.dart'; import '../../fcs_shipment/model/fcs_shipment_model.dart'; import '../../main/util.dart'; +import '../../widgets/box_size_picker.dart'; import '../../widgets/continue_button.dart'; import '../../widgets/local_dropdown.dart'; import '../../widgets/local_text.dart'; @@ -13,15 +18,29 @@ import '../../widgets/previous_button.dart'; typedef OnPrevious = Function(); +typedef OnContinue = Function(FcsShipment shipment, String cartonSizeType, + {CartonSize? standardSize, double? length, double? width, double? height}); + class TypeWidget extends StatefulWidget { final OnPrevious? onPrevious; - final bool isTransferFrom; - final bool isTransferTo; + final OnContinue? onContinue; + final FcsShipment? shipment; + final String cartonSizeType; + final CartonSize? standardSize; + final double? length; + final double? width; + final double? height; + const TypeWidget( {Key? key, this.onPrevious, - this.isTransferFrom = false, - this.isTransferTo = false}) + this.onContinue, + this.shipment, + required this.cartonSizeType, + this.standardSize, + this.length, + this.width, + this.height}) : super(key: key); @override @@ -30,14 +49,14 @@ class TypeWidget extends StatefulWidget { class _TypeWidgetState extends State { FcsShipment? _shipment; - List shipments = []; - List standardSizeList = [ - 'Large - 20”x20”x20”', - 'Medium - 15”x15”x15”', - 'Small - 10”x10”x10”' - ]; - int _cartinSizeValue = 1; - late String selectedValue; + String _cartionSizeType = standardCarton; + + List _shipments = []; + CartonSize? _selectStandardSize; + + TextEditingController _widthController = new TextEditingController(); + TextEditingController _heightController = new TextEditingController(); + TextEditingController _lengthController = new TextEditingController(); @override void initState() { @@ -46,10 +65,22 @@ class _TypeWidgetState extends State { } _init() async { - selectedValue = standardSizeList[1]; + _shipment = widget.shipment; + _cartionSizeType = widget.cartonSizeType; + + List cartonSizes = context.read().cartonSizes; + _selectStandardSize = widget.standardSize ?? cartonSizes.first; + + _lengthController.text = + widget.length == null ? "0" : widget.length.toString(); + _widthController.text = + widget.width == null ? "0" : widget.width.toString(); + _heightController.text = + widget.height == null ? "0" : widget.height.toString(); + var fcsShipments = await context.read().getActiveFcsShipments(); - shipments = fcsShipments; + _shipments = fcsShipments; if (mounted) { setState(() {}); @@ -58,11 +89,42 @@ class _TypeWidgetState extends State { @override Widget build(BuildContext context) { + List cartonSizes = context.watch().cartonSizes; + bool isStandardSize = _cartionSizeType == standardCarton; + bool isCustomSize = _cartionSizeType == customCarton; + bool isNoneDefinedSize = _cartionSizeType == packageCartion; + final continueBtn = ContinueButton(onTap: () { + double l = double.tryParse(_lengthController.text) ?? 0; + double w = double.tryParse(_widthController.text) ?? 0; + double h = double.tryParse(_heightController.text) ?? 0; + if (_shipment == null) { showMsgDialog(context, "Error", "Please select shipment"); return; } + + if (isStandardSize && + _selectStandardSize == null && + !isCustomSize && + !isNoneDefinedSize) { + showMsgDialog( + context, "Error", "Please select the standard cartion size"); + return; + } + + if (isCustomSize && + !isStandardSize && + !isNoneDefinedSize && + (l == 0 || w == 0 || h == 0)) { + showMsgDialog(context, "Error", "Please add the cartion size"); + return; + } + + if (widget.onContinue != null) { + widget.onContinue!(_shipment!, _cartionSizeType, + standardSize: _selectStandardSize, length: l, width: w, height: h); + } }); final previousBtn = PreviousButton(onTap: () { @@ -72,52 +134,83 @@ class _TypeWidgetState extends State { }); final standardSizeBox = Padding( - padding: const EdgeInsets.only(left: 35.0), - child: DropdownButton( - isDense: true, - value: selectedValue, - style: TextStyle(color: Colors.black, fontSize: 14), - underline: Container( - height: 1, - color: Colors.grey, + padding: const EdgeInsets.only(left: 34.0, top: 8), + child: IgnorePointer( + ignoring: !isStandardSize, + child: DropdownButton( + isDense: true, + value: _selectStandardSize, + style: TextStyle(color: Colors.black, fontSize: 14), + underline: Container(height: 1, color: Colors.grey), + onChanged: (newValue) { + setState(() { + _selectStandardSize = newValue!; + }); + }, + isExpanded: true, + items: + cartonSizes.map>((CartonSize value) { + return DropdownMenuItem( + value: value, + child: Row( + children: [ + Text("${value.name} - ", + style: TextStyle( + color: isStandardSize ? Colors.black : labelColor)), + Text( + "${value.length.toInt()}”x${value.width.toInt()}”x${value.height.toInt()}”", + style: TextStyle(color: labelColor)), + ], + ), + ); + }).toList(), ), - onChanged: (newValue) { - setState(() { - selectedValue = newValue!; - }); - }, - isExpanded: true, - items: standardSizeList.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value.toString(), - overflow: TextOverflow.ellipsis, - style: TextStyle(color: Colors.black)), - ); - }).toList(), + ), + ); + + final lengthBox = BoxSizePicker( + lableKey: 'box.length', + controller: _lengthController, + enable: isCustomSize); + + final widthBox = BoxSizePicker( + lableKey: 'box.width', + controller: _widthController, + enable: isCustomSize); + + final heightBox = BoxSizePicker( + lableKey: 'box.height', + controller: _heightController, + enable: isCustomSize); + + final customSizeBox = Padding( + padding: const EdgeInsets.only(left: 34.0), + child: Row( + children: [ + Flexible(child: lengthBox), + Flexible(child: widthBox), + Flexible(child: heightBox) + ], ), ); final cartonSizedBox = Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ // standard carton size InkWell( onTap: () { setState(() { - _cartinSizeValue = 1; + _cartionSizeType = standardCarton; }); }, child: Row(children: [ - Radio( - visualDensity: - const VisualDensity(horizontal: VisualDensity.minimumDensity), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - activeColor: primaryColor, - groupValue: _cartinSizeValue, - value: 1, - onChanged: (value) { + LocalRadio( + value: standardCarton, + groupValue: _cartionSizeType, + onChanged: (p0) { setState(() { - _cartinSizeValue = 1; + _cartionSizeType = standardCarton; }); }, ), @@ -125,32 +218,28 @@ class _TypeWidgetState extends State { child: Padding( padding: const EdgeInsets.only(left: 10), child: LocalText(context, 'box.standard_carton_size', - fontSize: 14, - color: _cartinSizeValue == 1 ? primaryColor : Colors.black), + fontSize: 15, + color: isStandardSize ? primaryColor : labelColor), ), ) ]), ), standardSizeBox, - const SizedBox(height: 10), + const SizedBox(height: 20), // custom size InkWell( onTap: () { setState(() { - _cartinSizeValue = 2; + _cartionSizeType = customCarton; }); }, child: Row(children: [ - Radio( - visualDensity: - const VisualDensity(horizontal: VisualDensity.minimumDensity), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - activeColor: primaryColor, - groupValue: _cartinSizeValue, - value: 2, - onChanged: (value) { + LocalRadio( + value: customCarton, + groupValue: _cartionSizeType, + onChanged: (p0) { setState(() { - _cartinSizeValue = 2; + _cartionSizeType = customCarton; }); }, ), @@ -158,30 +247,28 @@ class _TypeWidgetState extends State { child: Padding( padding: const EdgeInsets.only(left: 10), child: LocalText(context, 'box.custom_size', - fontSize: 14, - color: _cartinSizeValue == 2 ? primaryColor : Colors.black), + fontSize: 15, + color: isCustomSize ? primaryColor : Colors.black54), ), ) ]), ), + customSizeBox, + const SizedBox(height: 10), // not defined size InkWell( onTap: () { setState(() { - _cartinSizeValue = 3; + _cartionSizeType = packageCartion; }); }, child: Row(children: [ - Radio( - visualDensity: - const VisualDensity(horizontal: VisualDensity.minimumDensity), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - activeColor: primaryColor, - groupValue: _cartinSizeValue, - value: 3, - onChanged: (value) { + LocalRadio( + value: packageCartion, + groupValue: _cartionSizeType, + onChanged: (p0) { setState(() { - _cartinSizeValue = 3; + _cartionSizeType = packageCartion; }); }, ), @@ -189,12 +276,21 @@ class _TypeWidgetState extends State { child: Padding( padding: const EdgeInsets.only(left: 10), child: LocalText(context, 'box.package_size', - fontSize: 14, - color: _cartinSizeValue == 3 ? primaryColor : Colors.black), + fontSize: 15, + color: isNoneDefinedSize ? primaryColor : Colors.black54), ), ) ]), ), + Padding( + padding: const EdgeInsets.only(left: 34.0), + child: Text( + "No defined size", + style: TextStyle( + fontSize: 14, + color: isNoneDefinedSize ? Colors.black : labelColor), + ), + ) ], ); @@ -210,7 +306,7 @@ class _TypeWidgetState extends State { iconData: Ionicons.ios_airplane, display: (u) => u.shipmentNumber, selectedValue: _shipment, - values: shipments, + values: _shipments, )); return Column( @@ -220,9 +316,13 @@ class _TypeWidgetState extends State { child: ListView( padding: EdgeInsets.only(left: 10, right: 10), children: [ + const SizedBox(height: 8), 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 ], )), diff --git a/lib/pages/carton/model/carton_selection_model.dart b/lib/pages/carton/model/carton_selection_model.dart new file mode 100644 index 0000000..78e364c --- /dev/null +++ b/lib/pages/carton/model/carton_selection_model.dart @@ -0,0 +1,142 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:fcs/domain/entities/carton.dart'; +import 'package:logging/logging.dart'; + +import '../../../domain/constants.dart'; +import '../../main/model/base_model.dart'; + +class CartonSelectionModel extends BaseModel { + final log = Logger("CartonSelectionModel"); + // for search + String query = ""; + int offset = 0; + bool reachEnd = false; + List cartons = []; + + // for default carton + DocumentSnapshot? _lastDocument; + bool ended = false; + + List selectedCartonList = []; + + Timer? t; + search(String term, {bool imm = false, required String shipmentId}) async { + query = term; + cartons.clear(); + offset = 0; + reachEnd = false; + t?.cancel(); + t = Timer(Duration(milliseconds: imm ? 0 : 800), () async { + await loadMoreSearch(term: term, shipmentId: shipmentId); + }); + } + + Future loadMoreSearch( + {required String term, required String shipmentId}) async { + if (term == "") { + await _refresh(shipmentId); + 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(); + } + + addDefaultCartons(String shipmentId) async { + cartons.clear(); + await _refresh(shipmentId); + } + + selectCarton(Carton a) { + if (a.isSelected) { + selectedCartonList.add(a); + } else { + selectedCartonList.remove(a); + } + } + + Future _refresh(String shipmentId) async { + cartons.clear(); + _lastDocument = null; + ended = false; + await loadMoreData(shipmentId); + notifyListeners(); + } + + Future loadMoreData(String shipmentId) async { + int rowPerPage = 20; + + try { + String path = "/$cartons_collection"; + Query query = FirebaseFirestore.instance + .collection(path) + .where("fcs_shipment_id", isEqualTo: shipmentId) + // .where("status", isEqualTo: carton_processing_status) + // .where("carton_type", isEqualTo: carton_mix_box) + .where("is_deleted", isEqualTo: false) + .orderBy("created_at", 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 = Carton.fromMap(documentSnapshot.data() as Map, + documentSnapshot.id); + return p; + }).toList(); + + for (var p in list) { + selectedCartonList.contains(p) + ? p.isSelected = true + : p.isSelected = false; + } + + cartons.addAll(list); + if (list.length < rowPerPage) ended = true; + notifyListeners(); + } catch (e) { + log.warning("error:$e"); + } + } + + clearSelection() { + selectedCartonList.clear(); + cartons.clear(); + query = ""; + } +} diff --git a/lib/pages/widgets/box_size_picker.dart b/lib/pages/widgets/box_size_picker.dart new file mode 100644 index 0000000..b1a3531 --- /dev/null +++ b/lib/pages/widgets/box_size_picker.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +import '../../helpers/theme.dart'; +import 'input_text.dart'; +import 'local_text.dart'; + +class BoxSizePicker extends StatelessWidget { + final TextEditingController? controller; + final String lableKey; + final bool enable; + const BoxSizePicker( + {super.key, this.controller, required this.lableKey, this.enable = true}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocalText(context, lableKey, + color: enable ? Colors.black : labelColor, fontSize: 15), + Text("inches", + style: TextStyle(color: labelColor, fontSize: 12)) + ], + ), + ), + Flexible( + child: InputText( + isDense: true, + enabled: enable, + textAlign: TextAlign.end, + controller: controller, + withBorder: true, + textInputType: TextInputType.number, + constraints: BoxConstraints(maxWidth: 50, maxHeight: 40), + contentPadding: EdgeInsets.all(7), + borderColor: enable ? primaryColor : labelColor)) + ], + ), + ); + } +} diff --git a/lib/pages/widgets/display_text.dart b/lib/pages/widgets/display_text.dart index 46b80e7..feaca35 100644 --- a/lib/pages/widgets/display_text.dart +++ b/lib/pages/widgets/display_text.dart @@ -28,10 +28,9 @@ class DisplayText extends StatelessWidget { var languageModel = Provider.of(context); var labelStyle = languageModel.isEng - ? TextStyle( - color: Colors.black54, - ) - : TextStyle(color: Colors.black54, fontFamily: "Myanmar3"); + ? TextStyle(color: Colors.black54, fontSize: 15) + : TextStyle( + color: Colors.black54, fontFamily: "Myanmar3", fontSize: 15); var textStyle = languageModel.isEng ? TextStyle(color: Colors.black) : TextStyle(color: Colors.black, fontFamily: "Myanmar3"); diff --git a/lib/pages/widgets/input_text.dart b/lib/pages/widgets/input_text.dart index ca13218..99d97cc 100644 --- a/lib/pages/widgets/input_text.dart +++ b/lib/pages/widgets/input_text.dart @@ -16,6 +16,9 @@ class InputText extends StatelessWidget { final bool autoFocus; final TextAlign textAlign; final bool enabled; + final BoxConstraints? constraints; + final EdgeInsetsGeometry? contentPadding; + final bool? isDense; const InputText( {Key? key, @@ -29,7 +32,10 @@ class InputText extends StatelessWidget { this.autoFocus = false, this.textInputType, this.enabled = true, - this.textAlign = TextAlign.start}) + this.textAlign = TextAlign.start, + this.constraints, + this.contentPadding, + this.isDense}) : super(key: key); @override Widget build(BuildContext context) { @@ -47,6 +53,9 @@ class InputText extends StatelessWidget { keyboardType: textInputType, textAlign: textAlign, decoration: new InputDecoration( + isDense: isDense, + constraints: constraints, + contentPadding: contentPadding, // hintText: '', hintStyle: TextStyle( height: 1.5, @@ -65,16 +74,28 @@ class InputText extends StatelessWidget { ), enabledBorder: withBorder ? OutlineInputBorder( - borderSide: BorderSide(color: primaryColor, width: 1.0), + borderSide: BorderSide( + color: borderColor ?? primaryColor, width: 1.0), ) : UnderlineInputBorder( - borderSide: BorderSide(color: primaryColor, width: 1.0)), + borderSide: BorderSide( + color: borderColor ?? primaryColor, width: 1.0)), focusedBorder: withBorder ? OutlineInputBorder( - borderSide: BorderSide(color: primaryColor, width: 1.0), + borderSide: BorderSide( + color: borderColor ?? primaryColor, width: 1.0), ) : UnderlineInputBorder( - borderSide: BorderSide(color: primaryColor, width: 1.0)), + borderSide: BorderSide( + color: borderColor ?? primaryColor, width: 1.0)), + disabledBorder: withBorder + ? OutlineInputBorder( + borderSide: BorderSide( + color: borderColor ?? primaryColor, width: 1.0), + ) + : UnderlineInputBorder( + borderSide: BorderSide( + color: borderColor ?? primaryColor, width: 1.0)), ), validator: validator), ); diff --git a/lib/pages/widgets/local_dropdown.dart b/lib/pages/widgets/local_dropdown.dart index b693aec..fd32834 100644 --- a/lib/pages/widgets/local_dropdown.dart +++ b/lib/pages/widgets/local_dropdown.dart @@ -37,12 +37,14 @@ class LocalDropdown extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(right: 18.0), - child: labelKey!=null? LocalText( - context, - labelKey!, - color: Colors.black54, - fontSize: 16, - ): const SizedBox(), + child: labelKey != null + ? LocalText( + context, + labelKey!, + color: Colors.black54, + fontSize: 16, + ) + : const SizedBox(), ), DropdownButton( isDense: true, diff --git a/lib/pages/widgets/local_radio.dart b/lib/pages/widgets/local_radio.dart new file mode 100644 index 0000000..518de69 --- /dev/null +++ b/lib/pages/widgets/local_radio.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import '../../helpers/theme.dart'; + +class LocalRadio extends StatelessWidget { + final Function(T?)? onChanged; + final T value; + final T? groupValue; + + const LocalRadio( + {super.key, this.onChanged, required this.value, this.groupValue}); + + @override + Widget build(BuildContext context) { + return Radio( + fillColor: MaterialStateProperty.resolveWith((states) { + // active + if (states.contains(MaterialState.selected)) { + return primaryColor; + } + // inactive + return labelColor; + }), + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + activeColor: primaryColor, + groupValue: groupValue, + value: value, + onChanged: onChanged); + } +} diff --git a/lib/pages/widgets/local_radio_buttons.dart b/lib/pages/widgets/local_radio_buttons.dart index 6c2035d..22f72d1 100644 --- a/lib/pages/widgets/local_radio_buttons.dart +++ b/lib/pages/widgets/local_radio_buttons.dart @@ -33,6 +33,14 @@ class LocalRadioButtons extends StatelessWidget { onTap: () => callback!(e), child: Row(children: [ Radio( + fillColor: MaterialStateProperty.resolveWith((states) { + // active + if (states.contains(MaterialState.selected)) { + return primaryColor; + } + // inactive + return labelColor; + }), activeColor: primaryColor, groupValue: selectedValue, value: e, @@ -42,7 +50,7 @@ class LocalRadioButtons extends StatelessWidget { ), Text(e.toString(), style: TextStyle( - fontSize: 14, + fontSize: 15, color: e == selectedValue ? primaryColor : Colors.black)), ]), @@ -67,7 +75,14 @@ class LocalRadioButtons extends StatelessWidget { e == selectedValue ? primaryColor : Colors.grey, ), ), - Text(e.toString()), + Text( + e.toString(), + style: TextStyle( + fontSize: 15, + color: e == selectedValue + ? primaryColor + : Colors.black), + ), ]), ) : Container())