add carton editor for package

This commit is contained in:
tzw
2024-02-05 17:49:12 +06:30
parent afb980e6ae
commit a9e16ede8f
17 changed files with 1216 additions and 82 deletions

View File

@@ -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 ================================================================":"",

View File

@@ -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 ================================================================":"",

View File

@@ -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<App> {
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<App> {
..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<App> {
ChangeNotifierProvider.value(value: processingModel),
ChangeNotifierProvider.value(value: pickupModel),
ChangeNotifierProvider.value(value: cartonSelectionModel),
ChangeNotifierProvider.value(value: packageSelectionModel),
],
child: Consumer<LanguageModel>(
builder: (context, value, child) {

View File

@@ -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";

View File

@@ -14,6 +14,7 @@ class Package {
DateTime? currentStatusDate;
List<String> photoUrls;
List<ShipmentStatus> shipmentHistory;
List<String> 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<String, dynamic> 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<String> 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<String, dynamic> toJson() => {

View File

@@ -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<CargoType> cargoTypes, List<CargoType> customDuties);
typedef OnContinue = Function(
List<CargoType> cargoTypes, List<CargoType> customDuties);
class CargoWidget extends StatefulWidget {
final User sender;
final User consignee;
final List<CargoType> cargoTypes;
final List<CargoType> 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<CargoWidget> createState() => _CargoWidgetState();
}
class _CargoWidgetState extends State<CargoWidget> {
List<CargoType> _cargoTypes = [];
List<CargoType> _customDuties = [];
TextEditingController _totalCtl = TextEditingController();
List<TextEditingController> _cargoTypeControllers = [];
@override
void initState() {
_init();
super.initState();
}
_init() {
var model = context.read<ShipmentRateModel>();
_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<int> getEmptyFields() {
List<int> emptyFields = [];
for (int i = 0; i < _cargoTypeControllers.length; i++) {
if (isFieldEmpty(i)) {
emptyFields.add(i);
}
}
return emptyFields;
}
inputChangeListener() {
List<int> 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<CargoType>? customList = await Navigator.push<List<CargoType>>(
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)),
),
);
}
}

View File

@@ -90,7 +90,7 @@ class _CargoTableState extends State<CargoTable> {
),
),
DataCell(
c.isCutomDuty!
c.isCutomDuty
? GestureDetector(
onTap: () async {
String? _t = await showDialog(

View File

@@ -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<CartonEditorForPackage> createState() => _CartonEditorForPackageState();
@@ -36,18 +42,27 @@ class _CartonEditorForPackageState extends State<CartonEditorForPackage> {
LocalStep(lable: 'Cargos', stepType: StepType.CARGOS),
LocalStep(lable: 'Submit', stepType: StepType.SUBMIT)
];
List<Carton> _cartions = [];
List<Package> _packages = [];
List<CargoType> _cargoTypes = [];
List<CargoType> _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<PackageSelectionModel>().clearSelection();
super.initState();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
@@ -113,6 +128,7 @@ class _CartonEditorForPackageState extends State<CartonEditorForPackage> {
if (step.stepType == StepType.SIZE) {
return Expanded(
child: CartonSizeWidget(
deliveryType: _selectedDeliveryType,
sender: widget.sender,
consignee: widget.consignee,
shipment: _shipment,
@@ -124,9 +140,10 @@ class _CartonEditorForPackageState extends State<CartonEditorForPackage> {
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<CartonEditorForPackage> {
));
} 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;
// });
// },
// ),
);
}
}

View File

@@ -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<CartonSizeWidget> {
List<String> _deliveryTypes = [delivery_caton, pickup_carton];
FcsShipment? _shipment;
String _cartionSizeType = standardCarton;
List<FcsShipment> _shipments = [];
CartonSize? _selectStandardSize;
late String _selectedDeliveryType;
TextEditingController _widthController = new TextEditingController();
TextEditingController _heightController = new TextEditingController();
@@ -71,6 +77,7 @@ class _CartonSizeWidgetState extends State<CartonSizeWidget> {
}
_init() async {
_selectedDeliveryType = widget.deliveryType;
_shipment = widget.shipment;
_cartionSizeType = widget.cartonSizeType;
@@ -104,17 +111,21 @@ class _CartonSizeWidgetState extends State<CartonSizeWidget> {
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<CartonSizeWidget> {
],
);
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<CartonSizeWidget> {
!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<CartonSizeWidget> {
!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<CartonSizeWidget> {
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(

View File

@@ -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<CargoType> customDuties;
const CustomDutyAddition({super.key, required this.customDuties});
@override
_CustomDutyAdditionState createState() => _CustomDutyAdditionState();
}
class _CustomDutyAdditionState extends State<CustomDutyAddition> {
bool _isLoading = false;
List<CargoType> customDuties = [];
@override
void initState() {
_init();
super.initState();
}
_init() {
var shipmentRateModel =
Provider.of<ShipmentRateModel>(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<Widget> getCargoRowList(List<CargoType> _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: <Widget>[
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<CargoType> _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: <Widget>[
LocalTitle(
textKey: "box.select.cargo.title",
),
Column(
children: getCargoRowList(customDuties),
),
SizedBox(height: 30),
saveBtn,
SizedBox(height: 20),
],
),
),
),
);
}
}

View File

@@ -37,7 +37,7 @@ class _MixCartonEditorState extends State<MixCartonEditor> {
LocalStep(lable: 'Cartons', stepType: StepType.CARTONS),
LocalStep(lable: 'Submit', stepType: StepType.SUBMIT)
];
List<Carton> _cartions = [];
List<Carton> _cartons = [];
int currentStep = 0;
double _length = 0;
@@ -146,16 +146,16 @@ class _MixCartonEditorState extends State<MixCartonEditor> {
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<MixCartonEditor> {
width: _width,
height: _height,
shipment: _shipment!,
cartons: _cartions,
cartons: _cartons,
onCreate: () {
_create();
},

View File

@@ -109,7 +109,7 @@ class _TypeWidgetState extends State<TypeWidget> {
!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<TypeWidget> {
!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;
}

View File

@@ -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)

View File

@@ -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<Package> packages = [];
bool isLoading = false;
// for default package
DocumentSnapshot? _lastDocument;
bool ended = false;
List<Package> 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<void> 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<Carton> 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<void> _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<void> 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<Package> list = querySnap.docs.map((documentSnapshot) {
var p = Package.fromMap(documentSnapshot.data() as Map<String, dynamic>,
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 = "";
}
}

View File

@@ -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<void> 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<PackageSelectionModel>();
List<Package> 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<ScrollNotification>(
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: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0),
child: new Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
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<Color>(primaryColor)),
)),
]);
}
}

View File

@@ -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<Package> packages);
typedef OnContinue = Function(List<Package> packages);
class PackageSelectionWidget extends StatefulWidget {
final User sender;
final User consignee;
final FcsShipment shipment;
final List<Package> 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<PackageSelectionWidget> createState() => _PackageSelectionWidgetState();
}
class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
final TextEditingController _controller = TextEditingController();
String _query = "";
bool _isLoadMore = false;
final _scrollController = ScrollController();
@override
void initState() {
_init();
super.initState();
}
_init() {
var searchModel = context.read<PackageSelectionModel>();
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<void> _loadMoreData() async {
if (_isLoadMore) return;
var model = context.read<PackageSelectionModel>();
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<PackageSelectionModel>();
List<Package> searchResults = model.packages;
List<Package> 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<PackageSelectionModel>().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<CartonSelectionModel>()
.search(_query, imm: imm, shipmentId: widget.shipment.id!);
} catch (e) {
showMsgDialog(context, 'Error', e.toString());
}
}
}

View File

@@ -5,10 +5,11 @@ import 'package:flutter/material.dart';
class LocalTitle extends StatelessWidget {
final String? textKey;
final Widget? trailing;
final double topPadding;
final List<String>? 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!,