diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index e8d8a19..6bfd258 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -288,6 +288,7 @@ "Delivery Start ================================================================":"", "delivery.title":"Deliveries", "delivery":"Deliveries", + "delivery.info.title":"Deliverie", "delivery.popupmenu.active":"Active Cartons", "delivery.popupmenu.delivered":"Delivered Cartons", "Delivery End ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index f70bcd7..dbe8670 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -288,6 +288,7 @@ "Delivery Start ================================================================":"", "delivery.title":"ပေးပို့ရန်များ", "delivery":"ပေးပို့ရန်များ", + "delivery.info.title":"ပေးပို့ရန်", "delivery.popupmenu.active":"လာမည့် သေတ္တာများ", "delivery.popupmenu.delivered":"ပို့ပြီးသော သေတ္တာများ", "Delivery End ================================================================":"", diff --git a/lib/app.dart b/lib/app.dart index b5559d9..819b9a1 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -27,6 +27,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; +import 'pages/delivery/model/delivery_model.dart'; + class App extends StatefulWidget { @override _AppState createState() => _AppState(); @@ -51,6 +53,7 @@ class _AppState extends State { final DeliveryAddressModel deliveryAddressModel = new DeliveryAddressModel(); final PackageModel packageModel = new PackageModel(); final MarketModel marketModel = new MarketModel(); + final DeliveryModel deliveryModel = new DeliveryModel(); AppTranslationsDelegate _newLocaleDelegate; @@ -69,7 +72,8 @@ class _AppState extends State { ..addModel(boxModel) ..addModel(shipmentModel) ..addModel(invoiceModel) - ..addModel(marketModel); + ..addModel(marketModel) + ..addModel(deliveryModel); _newLocaleDelegate = AppTranslationsDelegate(newLocale: null); Translation().onLocaleChanged = onLocaleChange; @@ -113,6 +117,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: paymentMethodModel), ChangeNotifierProvider.value(value: marketModel), ChangeNotifierProvider.value(value: fcsShipmentModel), + ChangeNotifierProvider.value(value: deliveryModel), ], child: Consumer( builder: (context, value, child) { diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index b766af3..7d6d5ea 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -12,6 +12,7 @@ const cargo_types_collection = "cargo_types"; const custom_duties_collection = "custom_duties"; const discounts_by_weights_collection = "discounts_by_weight"; const shipments_collection = "shipments"; +const boxes_collection = "boxes"; // docs const setting_doc_id = "setting"; diff --git a/lib/domain/entities/box.dart b/lib/domain/entities/box.dart index d40974c..2956554 100644 --- a/lib/domain/entities/box.dart +++ b/lib/domain/entities/box.dart @@ -1,3 +1,4 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/domain/entities/discount_by_weight.dart'; import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/domain/vo/shipment_status.dart'; @@ -109,8 +110,8 @@ class Box { this.cartonType, this.fcsID, this.userName, - this.rate, - this.weight, + this.rate = 0, + this.weight = 0, this.packageType, this.pickUpID, this.remark, @@ -133,4 +134,21 @@ class Box { 'delivery_address': deliveryAddress.toMap(), }; } + + factory Box.fromMap(Map map, String docID) { + var _arrivedDate = (map['arrived_date'] as Timestamp); + + return Box( + id: docID, + arrivedDate: _arrivedDate != null ? _arrivedDate.toDate() : null, + shipmentNumber: map['shipment_number'], + receiverNumber: map['receiver_number'], + boxNumber: map['box_number'], + length: map['length'], + width: map['width'], + height: map['height'], + userName: map['user_name'], + fcsID: map['fcs_id'], + cartonType: map['carton_type']); + } } diff --git a/lib/pages/box/box_editor.dart b/lib/pages/box/box_editor.dart index 6c63c4a..568232f 100644 --- a/lib/pages/box/box_editor.dart +++ b/lib/pages/box/box_editor.dart @@ -66,8 +66,8 @@ class _BoxEditorState extends State { // for packages var packageModel = Provider.of(context, listen: false); _packages = [ - packageModel.packages[0], - packageModel.packages[1], + // packageModel.packages[0], + // packageModel.packages[1], ]; _packages.forEach((p) { p.isChecked = false; @@ -75,10 +75,18 @@ class _BoxEditorState extends State { //for shipment boxes var boxModel = Provider.of(context, listen: false); - _shipmentBoxes = [boxModel.boxes[0], boxModel.boxes[1], boxModel.boxes[2]]; + _shipmentBoxes = [ + boxModel.boxeList[0], + boxModel.boxeList[1], + boxModel.boxeList[2] + ]; //for mix boxes - _mixBoxes = [boxModel.boxes[0], boxModel.boxes[1], boxModel.boxes[2]]; + _mixBoxes = [ + boxModel.boxeList[0], + boxModel.boxeList[1], + boxModel.boxeList[2] + ]; _mixBoxes.forEach((b) { b.isChecked = false; }); @@ -101,6 +109,7 @@ class _BoxEditorState extends State { _lengthController.text = _box.length.toString(); _selectedCartonType = _box.cartonType; isNew = false; + user = User(fcsID: _box.fcsID, name: _box.userName); } else { _cargoTypes = [ CargoType(id: "1", name: 'General', weight: 25), diff --git a/lib/pages/box/box_info.dart b/lib/pages/box/box_info.dart index 40982c9..f3766f8 100644 --- a/lib/pages/box/box_info.dart +++ b/lib/pages/box/box_info.dart @@ -1,25 +1,18 @@ -import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/box.dart'; import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/vo/delivery_address.dart'; import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; -import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/defalut_delivery_address.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/fcs_id_icon.dart'; import 'package:fcs/pages/widgets/length_picker.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'; -import 'package:fcs/pages/widgets/multi_img_controller.dart'; -import 'package:fcs/pages/widgets/multi_img_file.dart'; import 'package:fcs/pages/widgets/progress.dart'; -import 'package:fcs/pages/widgets/status_tree.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; @@ -48,7 +41,7 @@ class _BoxInfoState extends State { String _shipmentNumber; List _packages = []; List _mixBoxes = []; - Box _selectedShipmentBox; + Box _selectedShipmentBox = new Box(); List _cargoTypes = []; DeliveryAddress _deliveryAddress = new DeliveryAddress(); TextEditingController _widthController = new TextEditingController(); @@ -66,8 +59,8 @@ class _BoxInfoState extends State { // for packages var packageModel = Provider.of(context, listen: false); _packages = [ - packageModel.packages[0], - packageModel.packages[1], + // packageModel.packages[0], + // packageModel.packages[1], ]; _packages.forEach((p) { p.isChecked = false; @@ -75,10 +68,14 @@ class _BoxInfoState extends State { //for shipment boxes var boxModel = Provider.of(context, listen: false); - _selectedShipmentBox = boxModel.boxes[0]; + _selectedShipmentBox = boxModel.boxeList[0]; //for mix boxes - _mixBoxes = [boxModel.boxes[0], boxModel.boxes[1], boxModel.boxes[2]]; + _mixBoxes = [ + boxModel.boxeList[0], + boxModel.boxeList[1], + boxModel.boxeList[2] + ]; _mixBoxes.forEach((b) { b.isChecked = false; }); @@ -115,6 +112,7 @@ class _BoxInfoState extends State { final DateFormat dateFormat = DateFormat("d MMM yyyy"); List _models() { + if (_box.shipmentHistory == null) return []; // return []; return _box.shipmentHistory .map((e) => TimelineModel( @@ -155,7 +153,19 @@ class _BoxInfoState extends State { @override Widget build(BuildContext context) { - var boxModel = Provider.of(context); + final cargoType = Container( + height: 30, + padding: EdgeInsets.only(left: 15), + child: Row( + children: [ + Icon(Icons.check_circle, color: primaryColor), + SizedBox( + width: 10, + ), + Text(_selectedCartonType), + ], + ), + ); final shipmentBox = DisplayText( text: _shipmentNumber == null ? "" : _shipmentNumber, @@ -464,10 +474,7 @@ class _BoxInfoState extends State { height: 10, ), LocalTitle(textKey: "box.type.title"), - LocalRadioButtons( - values: boxModel.cartonTypes, - selectedValue: _selectedCartonType, - callback: (v) {}), + cargoType, LocalTitle(textKey: "box.shipment_info"), shipmentBox, fcsIDBox, diff --git a/lib/pages/box/box_list.dart b/lib/pages/box/box_list.dart index 0b9daec..b5e2738 100644 --- a/lib/pages/box/box_list.dart +++ b/lib/pages/box/box_list.dart @@ -1,7 +1,5 @@ import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/localization/app_translations.dart'; import 'package:fcs/pages/box/model/box_model.dart'; -import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; @@ -20,11 +18,17 @@ class BoxList extends StatefulWidget { class _BoxListState extends State { bool _isLoading = false; - bool _showDelivered = false; + var _controller = ScrollController(); @override void initState() { super.initState(); + _controller.addListener(() async { + if (_controller.position.pixels == _controller.position.maxScrollExtent) { + Provider.of(context, listen: false).loadMore(); + } + }); + Provider.of(context, listen: false).initData(); } @override @@ -37,11 +41,17 @@ class _BoxListState extends State { var boxModel = Provider.of(context); final popupMenu = LocalPopupMenuButton( popmenus: [ - LocalPopupMenu(id: 1, textKey: "box.popupmenu.active", selected: true), - LocalPopupMenu(id: 2, textKey: "box.popupmenu.delivered") + LocalPopupMenu( + id: 1, + textKey: "box.popupmenu.active", + selected: boxModel.selectedIndex == 1), + LocalPopupMenu( + id: 2, + textKey: "box.popupmenu.delivered", + selected: boxModel.selectedIndex == 2) ], popupMenuCallback: (p) => this.setState(() { - _showDelivered = p.id == 2; + boxModel.selectedIndex = p.id; }), ); return LocalProgress( @@ -78,17 +88,41 @@ class _BoxListState extends State { label: LocalText(context, "boxes.new", color: Colors.white), backgroundColor: primaryColor, ), - body: new ListView.separated( - separatorBuilder: (context, index) => Divider( - color: Colors.black, - ), - scrollDirection: Axis.vertical, - padding: EdgeInsets.only(top: 15), - shrinkWrap: true, - itemCount: boxModel.boxes.length, - itemBuilder: (BuildContext context, int index) { - return BoxListRow(box: boxModel.boxes[index]); - }), + body: Column( + children: [ + Expanded( + child: RefreshIndicator( + child: new ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + controller: _controller, + separatorBuilder: (context, index) => Divider( + color: Colors.black, + ), + scrollDirection: Axis.vertical, + padding: EdgeInsets.only(top: 15), + shrinkWrap: true, + itemCount: boxModel.boxes.length, + itemBuilder: (BuildContext context, int index) { + return BoxListRow(box: boxModel.boxes[index]); + }), + onRefresh: () => boxModel.refresh(), + ), + ), + boxModel.isLoading + ? Container( + padding: EdgeInsets.all(8), + color: primaryColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Loading...", + style: TextStyle(color: Colors.white)), + ], + ), + ) + : Container(), + ], + ), ), ), ); diff --git a/lib/pages/box/box_list_row.dart b/lib/pages/box/box_list_row.dart index e8cce5b..0c98fc6 100644 --- a/lib/pages/box/box_list_row.dart +++ b/lib/pages/box/box_list_row.dart @@ -75,7 +75,7 @@ class _BoxListRowState extends State { children: [ Padding( padding: const EdgeInsets.all(0), - child: getStatus(_box.status), + child: getStatus(_box.status == null ? "" : _box.status), ), Padding( padding: const EdgeInsets.only(left: 8.0, top: 5, bottom: 5), diff --git a/lib/pages/box/model/box_model.dart b/lib/pages/box/model/box_model.dart index 60ba323..598b6eb 100644 --- a/lib/pages/box/model/box_model.dart +++ b/lib/pages/box/model/box_model.dart @@ -5,13 +5,22 @@ import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/box.dart'; import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/vo/message.dart'; import 'package:fcs/domain/vo/shipment_status.dart'; import 'package:fcs/domain/vo/delivery_address.dart'; +import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:logging/logging.dart'; class BoxModel extends BaseModel { + List _boxes = []; final log = Logger('BoxModel'); + List get boxes => + _selectedIndex == 1 ? _boxes : List.from(_delivered.values); + + Paginator _delivered; + int _selectedIndex = 1; + bool isLoading = false; StreamSubscription listener; static List statusHistory = [ @@ -26,7 +35,7 @@ class BoxModel extends BaseModel { // PackageModel.packages[2] ]; - List boxes = [ + List boxeList = [ Box( shipmentNumber: "A202", receiverNumber: "3", @@ -309,6 +318,97 @@ class BoxModel extends BaseModel { carton_mix_box ]; + set selectedIndex(int index) { + _selectedIndex = index; + notifyListeners(); + } + + get selectedIndex => _selectedIndex; + + initData() { + _selectedIndex = 1; + _loadBoxes(); + + if (_delivered != null) _delivered.close(); + // _delivered = _getDelivered(); + _delivered = _getDeliveredExample(); + _delivered.load(); + } + + int count = 0; + Paginator _getDeliveredExample() { + count = 1; + var pageQuery = Firestore.instance + .collection( + "/users/8OTfsbVvsUOn1SLxy1OrKk7Y_yNKkVoGalPcIlcHnAY/messages") + .orderBy("date", descending: true); + var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) { + var m = Message.fromMap(data, id); + return Box( + id: m.id, + shipmentNumber: m.message, + boxNumber: "1", + receiverNumber: "3", + rate: 0, + weight: 0, + arrivedDate: m.date, + ); + }); + return paginator; + } + + Future _loadBoxes() async { + if (user == null || !user.hasCarton()) return; + String path = "/$boxes_collection/"; + if (listener != null) listener.cancel(); + _boxes = []; + try { + listener = Firestore.instance + .collection("$path") + .snapshots() + .listen((QuerySnapshot snapshot) { + _boxes.clear(); + _boxes = snapshot.documents.map((documentSnapshot) { + var s = + Box.fromMap(documentSnapshot.data, documentSnapshot.documentID); + return s; + }).toList(); + notifyListeners(); + }); + } catch (e) { + log.warning("Error!! $e"); + } + } + + Paginator _getDelivered() { + if (user == null || !user.hasCarton()) return null; + + var pageQuery = Firestore.instance + .collection("/$boxes_collection") + .where("is_delivered", isEqualTo: true) + .where("is_deleted", isEqualTo: false); + var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) { + return Box.fromMap(data, id); + }); + return paginator; + } + + Future loadMore() async { + if (_delivered.ended) return; + isLoading = true; + notifyListeners(); + await _delivered.load(onFinished: () { + isLoading = false; + notifyListeners(); + }); + } + + Future refresh() async { + await _delivered.refresh(onFinished: () { + notifyListeners(); + }); + } + void initUser(user) { super.initUser(user); } @@ -316,6 +416,7 @@ class BoxModel extends BaseModel { @override logout() async { if (listener != null) await listener.cancel(); - boxes = []; + if (_delivered != null) _delivered.close(); + _boxes = []; } } diff --git a/lib/pages/delivery/delivery_info.dart b/lib/pages/delivery/delivery_info.dart new file mode 100644 index 0000000..c99a2bd --- /dev/null +++ b/lib/pages/delivery/delivery_info.dart @@ -0,0 +1,553 @@ +import 'package:fcs/domain/entities/box.dart'; +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/vo/delivery_address.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/delivery/model/delivery_model.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/package/model/package_model.dart'; +import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; +import 'package:fcs/pages/widgets/defalut_delivery_address.dart'; +import 'package:fcs/pages/widgets/display_text.dart'; +import 'package:fcs/pages/widgets/fcs_id_icon.dart'; +import 'package:fcs/pages/widgets/length_picker.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/local_title.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:timeline_list/timeline.dart'; +import 'package:timeline_list/timeline_model.dart'; + +final DateFormat dateFormat = DateFormat("d MMM yyyy"); + +class DeliveryInfo extends StatefulWidget { + final Box box; + DeliveryInfo({this.box}); + + @override + _DeliveryInfoState createState() => _DeliveryInfoState(); +} + +class _DeliveryInfoState extends State { + bool _isLoading = false; + Box _box; + String _selectedCartonType; + String _shipmentNumber; + List _packages = []; + List _mixBoxes = []; + Box _selectedShipmentBox = new Box(); + List _cargoTypes = []; + DeliveryAddress _deliveryAddress = new DeliveryAddress(); + TextEditingController _widthController = new TextEditingController(); + TextEditingController _heightController = new TextEditingController(); + TextEditingController _lengthController = new TextEditingController(); + double volumetricRatio = 0; + double shipmentWeight = 0; + + @override + void initState() { + super.initState(); + _box = widget.box; + _shipmentNumber = _box.shipmentNumber; + _selectedCartonType = _box.cartonType; + // for packages + var packageModel = Provider.of(context, listen: false); + _packages = [ + // packageModel.packages[0], + // packageModel.packages[1], + ]; + _packages.forEach((p) { + p.isChecked = false; + }); + + //for shipment boxes + var deliveryModel = Provider.of(context, listen: false); + _selectedShipmentBox = deliveryModel.cartonList[0]; + + //for mix carton + _mixBoxes = [ + deliveryModel.cartonList[0], + deliveryModel.cartonList[1], + deliveryModel.cartonList[2] + ]; + _mixBoxes.forEach((b) { + b.isChecked = false; + }); + + //for shipment weight + volumetricRatio = Provider.of(context, listen: false) + .rate + .volumetricRatio; + _lengthController.addListener(_calShipmentWeight); + _widthController.addListener(_calShipmentWeight); + _heightController.addListener(_calShipmentWeight); + + _widthController.text = _box.width.toString(); + _heightController.text = _box.height.toString(); + _lengthController.text = _box.length.toString(); + + _cargoTypes = _box.cargoTypes; + _deliveryAddress = _box.deliveryAddress; + } + + _calShipmentWeight() { + double l = double.parse(_lengthController.text, (s) => 0); + double w = double.parse(_widthController.text, (s) => 0); + double h = double.parse(_heightController.text, (s) => 0); + setState(() { + shipmentWeight = l * w * h / volumetricRatio; + }); + } + + @override + void dispose() { + super.dispose(); + } + + final DateFormat dateFormat = DateFormat("d MMM yyyy"); + List _models() { + if (_box.shipmentHistory == null) return []; + // return []; + return _box.shipmentHistory + .map((e) => TimelineModel( + Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(e.status, + style: TextStyle( + color: e.done ? primaryColor : Colors.grey, + fontSize: 16, + fontWeight: FontWeight.bold)), + e.status == "Processed" + ? Text("(Waiting for payment)", + style: TextStyle( + color: e.done ? primaryColor : Colors.grey, + fontSize: 14, + fontWeight: FontWeight.bold)) + : Container(), + Text(dateFormat.format(e.date)), + ], + ), + ), + iconBackground: e.done ? primaryColor : Colors.grey, + icon: Icon( + e.status == "Shipped" + ? Ionicons.ios_airplane + : e.status == "Delivered" + ? MaterialCommunityIcons.truck_fast + : e.status == "Processed" + ? MaterialIcons.check + : Octicons.package, + color: Colors.white, + ))) + .toList(); + } + + @override + Widget build(BuildContext context) { + final cargoType = Container( + height: 30, + padding: EdgeInsets.only(left: 15), + child: Row( + children: [ + Icon(Icons.check_circle, color: primaryColor), + SizedBox( + width: 10, + ), + Text(_selectedCartonType), + ], + ), + ); + + final shipmentBox = DisplayText( + text: _shipmentNumber == null ? "" : _shipmentNumber, + labelTextKey: "box.fcs_shipment_num", + iconData: Ionicons.ios_airplane, + ); + final fcsIDBox = DisplayText( + text: _box.fcsID == null ? "" : _box.fcsID, + labelTextKey: "box.fcs.id", + icon: FcsIDIcon(), + ); + + final customerNameBox = DisplayText( + text: _box.userName == null ? "" : _box.userName, + labelTextKey: "box.name", + iconData: Icons.person, + ); + + final packageTitle = Container( + padding: EdgeInsets.only(left: 15, right: 10.0, top: 20), + child: Row( + children: [ + Expanded( + child: LocalText(context, 'box.tracking.id', color: Colors.grey), + ), + LocalText(context, 'box.package.desc', color: Colors.grey), + ], + ), + ); + + List getPackageRowList() { + return _packages.asMap().entries.map((p) { + return Container( + color: Colors.grey[50].withOpacity(0.2), + child: Container( + padding: + EdgeInsets.only(left: 15.0, right: 10.0, top: 5.0, bottom: 5.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: p.key == _packages.length - 1 + ? Colors.white + : Colors.grey[350], + width: 1), + ), + ), + child: Row( + children: [ + Expanded( + child: new Text( + p.value.trackingID, + style: textStyle, + )), + new Column( + children: [ + new Text( + p.value.desc == null ? "" : p.value.desc, + style: textStyle, + ), + new Text( + "(${p.value.market == null ? "" : p.value.market})", + style: textStyle, + ) + ], + ) + ], + ), + ), + ); + }).toList(); + } + + final shipmentBoxTitle = Container( + padding: EdgeInsets.only(left: 15, right: 10.0, top: 20), + child: Row( + children: [ + Expanded( + child: + LocalText(context, 'box.shipment_number', color: Colors.grey), + ), + LocalText(context, 'box.shipment.desc', color: Colors.grey), + ], + ), + ); + + final shipmentBoxRow = Container( + padding: EdgeInsets.only(left: 15.0, right: 10.0, top: 5.0, bottom: 5.0), + child: Row( + children: [ + Expanded( + child: new Text( + _selectedShipmentBox.shipmentNumber == null + ? "" + : _selectedShipmentBox.shipmentNumber, + style: textStyle, + )), + new Text( + _selectedShipmentBox.desc == null ? "" : _selectedShipmentBox.desc, + style: textStyle, + ), + ], + ), + ); + + final mixBoxTitle = Container( + padding: EdgeInsets.only(left: 15, right: 10.0, top: 20), + child: Row( + children: [ + Expanded( + child: LocalText(context, 'box.mix.number', color: Colors.grey), + ), + LocalText(context, 'box.mix.desc', color: Colors.grey), + ], + ), + ); + + List getMixBoxRowList() { + return _mixBoxes.asMap().entries.map((b) { + return Container( + color: Colors.grey[50].withOpacity(0.2), + child: Container( + padding: EdgeInsets.only( + left: 15.0, right: 10.0, top: 13.0, bottom: 13.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: b.key == _mixBoxes.length - 1 + ? Colors.white + : Colors.grey[350], + width: 1), + ), + ), + child: Row( + children: [ + Expanded( + child: new Text( + b.value.packageNumber, + style: textStyle, + )), + new Text( + b.value.desc == null ? "" : b.value.desc, + style: textStyle, + ), + ], + ), + ), + ); + }).toList(); + } + + final cargoTitle = Container( + padding: EdgeInsets.only(left: 15, right: 0.0, top: 20), + child: Row( + children: [ + Expanded( + child: LocalText(context, 'cargo.type', color: Colors.grey), + ), + Container( + padding: EdgeInsets.only(right: 10), + child: LocalText(context, 'cargo.weight', color: Colors.grey)), + ], + ), + ); + + List getCargoRowList() { + if (_cargoTypes == null) { + return []; + } + double total = 0; + + var rows = _cargoTypes.asMap().entries.map((c) { + total += c.value.weight; + return InkWell( + onTap: () {}, + child: Container( + color: Colors.grey[50].withOpacity(0.2), + child: Container( + padding: EdgeInsets.only( + left: 15.0, right: 10.0, top: 13.0, bottom: 13.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey[350], width: 1), + ), + ), + child: Row( + children: [ + Expanded( + child: new Text( + c.value.name, + style: textStyle, + )), + Container( + padding: EdgeInsets.only(right: 10), + child: new Text( + c.value.weight == null ? "0" : c.value.weight.toString(), + style: textStyle, + ), + ) + ], + ), + ), + ), + ); + }).toList(); + + var totalRow = InkWell( + child: Container( + color: Colors.grey[50].withOpacity(0.2), + child: Container( + padding: + EdgeInsets.only(left: 15.0, right: 10.0, top: 15.0, bottom: 15.0), + child: Row( + children: [ + Expanded( + child: new Text( + "Total Weight", + style: TextStyle(fontWeight: FontWeight.bold), + )), + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Align( + alignment: Alignment.centerRight, + child: new Text( + total.toString(), + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ), + )); + + rows.add(totalRow); + return rows; + } + + final lengthBox = LengthPicker( + controller: _lengthController, + lableKey: "box.length", + isReadOnly: true, + ); + final widthBox = LengthPicker( + controller: _widthController, + lableKey: "box.width", + isReadOnly: true, + ); + final heightBox = LengthPicker( + controller: _heightController, + lableKey: "box.height", + isReadOnly: true, + ); + + final dimBox = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Icon(FontAwesome.arrow_circle_right, color: primaryColor), + ), + SizedBox(child: lengthBox, width: 80), + SizedBox(child: widthBox, width: 80), + SizedBox(child: heightBox, width: 80), + ], + ); + + final shipmentWeightBox = DisplayText( + text: shipmentWeight != null ? shipmentWeight.toStringAsFixed(0) : "", + labelTextKey: "box.shipment_weight", + iconData: MaterialCommunityIcons.weight, + ); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), + onPressed: () => Navigator.of(context).pop(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "delivery.info.title", + fontSize: 20, + color: primaryColor, + ), + // actions: [ + // IconButton( + // icon: Icon(Icons.edit, color: primaryColor), + // onPressed: _gotoEditor, + // ), + // ], + ), + body: Card( + child: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListView(children: [ + Center(child: nameWidget(_box.packageNumber)), + SizedBox( + height: 10, + ), + LocalTitle(textKey: "box.type.title"), + cargoType, + LocalTitle(textKey: "box.shipment_info"), + shipmentBox, + fcsIDBox, + customerNameBox, + _selectedCartonType == "From packages" + ? Column( + children: [ + LocalTitle(textKey: "box.packages"), + packageTitle, + Divider( + color: Colors.grey[400], + ), + Column( + children: getPackageRowList(), + ), + ], + ) + : _selectedCartonType == "From shipments" + ? Column( + children: [ + LocalTitle(textKey: "box.shipment.boxes"), + shipmentBoxTitle, + Divider( + color: Colors.grey[400], + ), + shipmentBoxRow + ], + ) + : _selectedCartonType == "Mix carton" + ? Column( + children: [ + LocalTitle(textKey: "box.shipment.boxes"), + mixBoxTitle, + Divider( + color: Colors.grey[400], + ), + Column( + children: getMixBoxRowList(), + ) + ], + ) + : Container(), + LocalTitle(textKey: "box.cargo_type"), + cargoTitle, + Divider( + color: Colors.grey[400], + ), + Column( + children: getCargoRowList(), + ), + LocalTitle(textKey: "box.dimension"), + dimBox, + shipmentWeightBox, + LocalTitle(textKey: "box.delivery_address"), + DefaultDeliveryAddress( + deliveryAddress: _deliveryAddress, + labelKey: "box.delivery_address", + ), + LocalTitle(textKey: "box.status"), + Container( + height: 230, + child: Timeline( + children: _models(), position: TimelinePosition.Left), + ), + SizedBox( + height: 20, + ) + ]), + )), + ], + ), + ), + ), + ); + } + + _gotoEditor() async {} +} diff --git a/lib/pages/delivery/delivery_list.dart b/lib/pages/delivery/delivery_list.dart index 6e1b557..796e5bf 100644 --- a/lib/pages/delivery/delivery_list.dart +++ b/lib/pages/delivery/delivery_list.dart @@ -1,6 +1,4 @@ import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/localization/app_translations.dart'; -import 'package:fcs/pages/box/model/box_model.dart'; import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; @@ -10,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'delivery_list_row.dart'; +import 'model/delivery_model.dart'; class DeliverList extends StatefulWidget { @override @@ -18,11 +17,17 @@ class DeliverList extends StatefulWidget { class _DeliverListState extends State { bool _isLoading = false; - bool _showDelivered = false; + var _controller = ScrollController(); @override void initState() { super.initState(); + _controller.addListener(() async { + if (_controller.position.pixels == _controller.position.maxScrollExtent) { + Provider.of(context, listen: false).loadMore(); + } + }); + Provider.of(context, listen: false).initData(); } @override @@ -32,14 +37,20 @@ class _DeliverListState extends State { @override Widget build(BuildContext context) { - var boxModel = Provider.of(context); + var deliveryModel = Provider.of(context); final popupMenu = LocalPopupMenuButton( popmenus: [ - LocalPopupMenu(id: 1, textKey: "delivery.popupmenu.active", selected: true), - LocalPopupMenu(id: 2, textKey: "delivery.popupmenu.delivered") + LocalPopupMenu( + id: 1, + textKey: "delivery.popupmenu.active", + selected: deliveryModel.selectedIndex == 1), + LocalPopupMenu( + id: 2, + textKey: "delivery.popupmenu.delivered", + selected: deliveryModel.selectedIndex == 2) ], popupMenuCallback: (p) => this.setState(() { - _showDelivered = p.id == 2; + deliveryModel.selectedIndex = p.id; }), ); return LocalProgress( @@ -68,17 +79,42 @@ class _DeliverListState extends State { popupMenu ], ), - body: new ListView.separated( - separatorBuilder: (context, index) => Divider( - color: Colors.black, - ), - scrollDirection: Axis.vertical, - padding: EdgeInsets.only(top: 15), - shrinkWrap: true, - itemCount: boxModel.boxes.length, - itemBuilder: (BuildContext context, int index) { - return DeliveryListRow(box: boxModel.boxes[index]); - }), + body: Column( + children: [ + Expanded( + child: RefreshIndicator( + child: new ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + controller: _controller, + separatorBuilder: (context, index) => Divider( + color: Colors.black, + ), + scrollDirection: Axis.vertical, + padding: EdgeInsets.only(top: 15), + shrinkWrap: true, + itemCount: deliveryModel.cartons.length, + itemBuilder: (BuildContext context, int index) { + return DeliveryListRow( + box: deliveryModel.cartons[index]); + }), + onRefresh: () => deliveryModel.refresh(), + ), + ), + deliveryModel.isLoading + ? Container( + padding: EdgeInsets.all(8), + color: primaryColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Loading...", + style: TextStyle(color: Colors.white)), + ], + ), + ) + : Container(), + ], + ), ), ), ); diff --git a/lib/pages/delivery/delivery_list_row.dart b/lib/pages/delivery/delivery_list_row.dart index fd7eeef..2f33e9b 100644 --- a/lib/pages/delivery/delivery_list_row.dart +++ b/lib/pages/delivery/delivery_list_row.dart @@ -5,6 +5,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'delivery_info.dart'; + class DeliveryListRow extends StatefulWidget { final Box box; const DeliveryListRow({this.box}); @@ -32,7 +34,7 @@ class _DeliveryListRowState extends State { onTap: () { Navigator.push( context, - CupertinoPageRoute(builder: (context) => BoxEditor(box: _box)), + CupertinoPageRoute(builder: (context) => DeliveryInfo(box: _box)), ); }, child: Row( @@ -75,7 +77,7 @@ class _DeliveryListRowState extends State { children: [ Padding( padding: const EdgeInsets.all(0), - child: getStatus(_box.status), + child: getStatus(_box.status == null ? "" : _box.status), ), Padding( padding: const EdgeInsets.only(left: 8.0, top: 5, bottom: 5), diff --git a/lib/pages/delivery/model/delivery_model.dart b/lib/pages/delivery/model/delivery_model.dart new file mode 100644 index 0000000..9e165bd --- /dev/null +++ b/lib/pages/delivery/model/delivery_model.dart @@ -0,0 +1,395 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:fcs/domain/constants.dart'; +import 'package:fcs/domain/entities/box.dart'; +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/vo/message.dart'; +import 'package:fcs/domain/vo/shipment_status.dart'; +import 'package:fcs/domain/vo/delivery_address.dart'; +import 'package:fcs/helpers/paginator.dart'; +import 'package:fcs/pages/main/model/base_model.dart'; +import 'package:logging/logging.dart'; + +class DeliveryModel extends BaseModel { + List _cartons = []; + final log = Logger('BoxModel'); + List get cartons => + _selectedIndex == 1 ? _cartons : List.from(_delivered.values); + + Paginator _delivered; + int _selectedIndex = 1; + bool isLoading = false; + + StreamSubscription listener; + static List statusHistory = [ + ShipmentStatus(status: "Packed", date: DateTime(2020, 6, 1), done: true), + ShipmentStatus(status: "Shipped", date: DateTime(2020, 6, 5), done: false), + ShipmentStatus( + status: "Delivered", date: DateTime(2020, 6, 15), done: false) + ]; + static List packages = [ + // PackageModel.packages[0], + // PackageModel.packages[1], + // PackageModel.packages[2] + ]; + + List cartonList = [ + Box( + shipmentNumber: "A202", + receiverNumber: "3", + receiverName: "Ko Myo Min", + boxNumber: "1", + rate: 7, + packageType: "General", + weight: 75, + status: "Packed", + receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon', + cargoDesc: "Clothes", + desc: "Desc 1", + cartonType: carton_from_packages, + arrivedDate: DateTime(2020, 6, 1), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + packages: packages, + shipmentHistory: statusHistory, + deliveryAddress: DeliveryAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A203", + receiverNumber: "3", + receiverName: "Ko Myo Min", + boxNumber: "2", + rate: 7, + packageType: "General", + weight: 75, + status: "Packed", + cargoDesc: "Clothes", + desc: "Desc 2", + arrivedDate: DateTime(2020, 6, 1), + width: 10, + height: 20, + length: 30, + shipmentWeight: 36, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon', + cartonType: carton_from_shipments, + deliveryAddress: DeliveryAddress( + fullName: 'Mg Myo', + addressLine1: '153-154 5th Thitsar.', + addressLine2: 'South Okkalapa Township', + city: 'Yangon', + state: 'Myanmar', + phoneNumber: '+09 95724 8750'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A204", + receiverNumber: "3", + receiverName: "Ko Myo Min", + boxNumber: "3", + rate: 7, + packageType: "General", + weight: 75, + cargoDesc: "Shoes", + status: "Packed", + desc: "Desc 3", + cartonType: carton_mix_box, + arrivedDate: DateTime(2020, 6, 1), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon', + deliveryAddress: DeliveryAddress( + fullName: 'Mg Myo', + addressLine1: '153-154 5th Thitsar.', + addressLine2: 'South Okkalapa Township', + city: 'Yangon', + state: 'Myanmar', + phoneNumber: '+09 95724 8750'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A202", + receiverNumber: "2", + receiverName: "Ma Aye", + boxNumber: "1", + rate: 8, + packageType: "Medicine", + weight: 75, + status: "Packed", + cargoDesc: "Dietary supplement", + desc: "Desc 4", + arrivedDate: DateTime(2020, 6, 1), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '2 Shwe Taung Kyar St, Bahan Tsp, Yangon', + deliveryAddress: DeliveryAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A202", + receiverNumber: "2", + receiverName: "Ma Aye", + boxNumber: "3", + rate: 7, + packageType: "General", + cargoDesc: "Handbags", + weight: 75, + status: "Arrived", + arrivedDate: DateTime(2020, 6, 1), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '2 Shwe Taung Kyar St, Bahan Tsp, Yangon', + deliveryAddress: DeliveryAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A202", + receiverNumber: "2", + receiverName: "Ma Aye", + boxNumber: "2", + rate: 7, + packageType: "General", + cargoDesc: "Handbags", + weight: 75, + status: "Shipped", + arrivedDate: DateTime(2020, 6, 1), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '2 Shwe Taung Kyar St, Bahan Tsp, Yangon', + deliveryAddress: DeliveryAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A201", + receiverNumber: "1", + receiverName: "Ko Wai", + boxNumber: "1", + rate: 9, + packageType: "Dangerous", + cargoDesc: "Phones and Scooters", + weight: 75, + status: "Delivered", + arrivedDate: DateTime(2020, 5, 21), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon', + deliveryAddress: DeliveryAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + Box( + shipmentNumber: "A201", + receiverNumber: "1", + receiverName: "Ko Wai", + boxNumber: "2", + rate: 7, + packageType: "General", + cargoDesc: "Construction tools", + weight: 75, + status: "Delivered", + arrivedDate: DateTime(2020, 5, 21), + width: 10, + height: 10, + length: 10, + shipmentWeight: 6, + shipmentHistory: statusHistory, + packages: packages, + receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon', + deliveryAddress: DeliveryAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'), + cargoTypes: [ + CargoType(name: 'General', weight: 25), + CargoType(name: 'Medicine', weight: 20), + CargoType(name: 'Dangerous', weight: 30) + ]), + ]; + + List cartonTypes = [ + carton_from_packages, + carton_from_shipments, + carton_mix_box + ]; + + set selectedIndex(int index) { + _selectedIndex = index; + notifyListeners(); + } + + get selectedIndex => _selectedIndex; + + initData() { + _selectedIndex = 1; + _loadBoxes(); + + if (_delivered != null) _delivered.close(); + // _delivered = _getDelivered(); + _delivered = _getDeliveredExample(); + _delivered.load(); + } + + int count = 0; + Paginator _getDeliveredExample() { + count = 1; + var pageQuery = Firestore.instance + .collection( + "/users/8OTfsbVvsUOn1SLxy1OrKk7Y_yNKkVoGalPcIlcHnAY/messages") + .orderBy("date", descending: true); + var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) { + var m = Message.fromMap(data, id); + return Box( + id: m.id, + shipmentNumber: m.message, + boxNumber: "1", + receiverNumber: "3", + rate: 0, + weight: 0, + arrivedDate: m.date, + ); + }); + return paginator; + } + + Future _loadBoxes() async { + if (user == null || !user.hasDeliveries()) return; + String path = "/$boxes_collection/"; + if (listener != null) listener.cancel(); + _cartons = []; + try { + listener = Firestore.instance + .collection("$path") + .snapshots() + .listen((QuerySnapshot snapshot) { + _cartons.clear(); + _cartons = snapshot.documents.map((documentSnapshot) { + var s = + Box.fromMap(documentSnapshot.data, documentSnapshot.documentID); + return s; + }).toList(); + notifyListeners(); + }); + } catch (e) { + log.warning("Error!! $e"); + } + } + + Paginator _getDelivered() { + if (user == null || !user.hasDeliveries()) return null; + + var pageQuery = Firestore.instance + .collection("/$boxes_collection") + .where("is_delivered", isEqualTo: true) + .where("is_deleted", isEqualTo: false); + var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) { + return Box.fromMap(data, id); + }); + return paginator; + } + + Future loadMore() async { + if (_delivered.ended) return; + isLoading = true; + notifyListeners(); + await _delivered.load(onFinished: () { + isLoading = false; + notifyListeners(); + }); + } + + Future refresh() async { + await _delivered.refresh(onFinished: () { + notifyListeners(); + }); + } + + void initUser(user) { + super.initUser(user); + } + + @override + logout() async { + if (listener != null) await listener.cancel(); + if (_delivered != null) _delivered.close(); + _cartons = []; + } +}