diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index dec1e6e..c259096 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -126,6 +126,7 @@ "customer.invitation.request.confirm":"Accept customer", "customer.disable.btn":"Disable", "customer.enable.btn":"Enable", + "customer.chat.btn":"Chat", "Customer End ================================================================":"", "Invitation Start ================================================================":"", @@ -584,10 +585,15 @@ "pickup.to_time":"To Time", "pickup.status":"Status", "pickup.desc":"Description", - "pickup.remark":"Remark", + "pickup.complete.remark":"Complete Remark", "pickup.staff.name":"Staff", "pickup.edit.complete.btn":"Complete pickup", "pickup.continue.btn":"Continue to complete", "pickup.confirm.complete":"Complete confirm?", + "pickup.zone":"Pickup Zone", + "pickup.customer":"Customer", + "pickup.customer.remark":"Customer Remark", + "pickup.reschedul.remark":"Reschedule Remark", + "pickup.delivery.address":"Delivery address", "Pickup End ===================================================================":"" } \ No newline at end of file diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index 73cfb65..ddbb6c0 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -126,6 +126,7 @@ "customer.invitation.request.confirm":"လက်ခံ လိုက်ပါ", "customer.disable.btn":"ပိတ်ပါ", "customer.enable.btn":"ဖွင့်ပါ", + "customer.chat.btn":"Chat", "Customer End ================================================================":"", "Invitation Start ================================================================":"", @@ -582,10 +583,15 @@ "pickup.to_time":"အချိန်(တွင်း)", "pickup.status":"အခြေအနေ", "pickup.desc":"ဖော်ပြချက်", - "pickup.remark":"မှတ်ချက်", + "pickup.complete.remark":"Complete Remark", "pickup.staff.name":"ဝန်ထမ်း", "pickup.edit.complete.btn":"ပို့ဆောင်ခြင်း ပြီးဆုံးသည်", "pickup.continue.btn":"Continue to complete", "pickup.confirm.complete":"Complete confirm?", + "pickup.zone":"Pickup Zone", + "pickup.customer":"ဝယ်သူ", + "pickup.customer.remark":"ဝယ်သူ မှတ်ချက်", + "pickup.reschedul.remark":"Reschedule Remark", + "pickup.delivery.address":"ပို့ဆောင်ရမည့်လိပ်စာ", "Pickup End ===================================================================":"" } \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 4b08f81..7149625 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -17,6 +17,7 @@ import 'package:fcs/pages/main/model/language_model.dart'; import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/payment_methods/model/payment_method_model.dart'; +import 'package:fcs/pages/pickup/model/pickup_model.dart'; import 'package:fcs/pages/processing/model/processing_model.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; import 'package:fcs/pages/shipment/model/shipment_model.dart'; @@ -58,6 +59,7 @@ class _AppState extends State { final DeliveryModel deliveryModel = new DeliveryModel(); final CartonSizeModel cartonSizeModel = new CartonSizeModel(); final ProcessingModel processingModel = new ProcessingModel(); + final PickupModel pickupModel = new PickupModel(); late AppTranslationsDelegate _newLocaleDelegate; @@ -79,7 +81,8 @@ class _AppState extends State { ..addModel(marketModel) ..addModel(deliveryModel) ..addModel(cartonSizeModel) - ..addModel(processingModel); + ..addModel(processingModel) + ..addModel(pickupModel); _newLocaleDelegate = AppTranslationsDelegate( newLocale: Translation().supportedLocales().first); @@ -127,6 +130,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: deliveryModel), ChangeNotifierProvider.value(value: cartonSizeModel), ChangeNotifierProvider.value(value: processingModel), + ChangeNotifierProvider.value(value: pickupModel), ], child: Consumer( builder: (context, value, child) { diff --git a/lib/data/provider/pickup_data_provider.dart b/lib/data/provider/pickup_data_provider.dart index 94a73f5..e28df19 100644 --- a/lib/data/provider/pickup_data_provider.dart +++ b/lib/data/provider/pickup_data_provider.dart @@ -1,13 +1,12 @@ import 'dart:convert'; -import 'package:fcs/config.dart'; -import 'package:fcs/domain/constants.dart'; -import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/pickup.dart'; import 'package:fcs/helpers/api_helper.dart'; import 'package:fcs/helpers/firebase_helper.dart'; import 'package:logging/logging.dart'; +import '../../config.dart'; + class PickupDataProvider { final log = Logger('PickupDataProvider'); static final PickupDataProvider instance = PickupDataProvider._(); @@ -17,4 +16,34 @@ class PickupDataProvider { return await requestAPI("/pickups/complete", "PUT", payload: pickup.toMapForComplete(), token: await getToken()); } + + Future> searchPickup(String term) async { + if (term == null || term == '') return []; + + List pickups = []; + + try { + var data = { + "filters": [ + { + "field": "pickup_number", + "compare": "like", + "value": "%" + term.toUpperCase() + "%" + } + ] + }; + var result = await requestAPI("/api/data/pickups", "POST", + token: await getToken(), + url: Config.instance.reportURL, + payload: jsonEncode(data)); + if (result == null) return pickups; + result.forEach((d) { + var package = Pickup.fromJson(d); + pickups.add(package); + }); + } catch (e) { + log.warning("Error >>>>${e.toString()}"); + } + return pickups; + } } diff --git a/lib/data/services/pickup_imp.dart b/lib/data/services/pickup_imp.dart index 09b6f4a..3d998f3 100644 --- a/lib/data/services/pickup_imp.dart +++ b/lib/data/services/pickup_imp.dart @@ -16,4 +16,9 @@ class PickupServiceImp implements PickupService { Future completePickup(Pickup pickup) { return pickupDataProvider.completePickup(pickup); } + + @override + Future> searchPickup(String term) { + return pickupDataProvider.searchPickup(term); + } } diff --git a/lib/data/services/pickup_service.dart b/lib/data/services/pickup_service.dart index 39cd356..676bfde 100644 --- a/lib/data/services/pickup_service.dart +++ b/lib/data/services/pickup_service.dart @@ -1,6 +1,6 @@ -import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/pickup.dart'; abstract class PickupService { Future completePickup(Pickup pickup); + Future> searchPickup(String term); } diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index 0bebe98..7641213 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -18,6 +18,8 @@ const discounts_by_weights_collection = "discounts_by_weight"; const shipments_collection = "shipments"; const cartons_collection = "cartons"; const discounts_collection = "discounts"; +const pickup_collection = "pickups"; + // docs const setting_doc_id = "setting"; diff --git a/lib/domain/entities/pickup.dart b/lib/domain/entities/pickup.dart index 6f36444..892a07a 100644 --- a/lib/domain/entities/pickup.dart +++ b/lib/domain/entities/pickup.dart @@ -1,24 +1,28 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/vo/delivery_address.dart'; class Pickup { String? id; DateTime? pickupDate; - String? fromTime; - String? toTime; - //for consignee - String? userID; - String? userName; - String? userPhoneNumber; + String? pickupNumber; + DateTime? fromTime; + DateTime? toTime; + //for customer + String? customerID; + String? customerName; + String? customerRemark; String? staffId; String? staffName; String? staffPhoneNumber; - //for shipper + String? fcsID; - String? shipperName; - String? shipperPhoneNumber; String? status; + String? rescheduleRemark; + String? zoneID; + String? zoneName; + DeliveryAddress? pickupAddress; // for complete String? completeRemark; @@ -28,9 +32,8 @@ class Pickup { Pickup( {this.id, - this.userID, - this.userName, - this.userPhoneNumber, + this.customerID, + this.customerName, this.staffId, this.staffName, this.staffPhoneNumber, @@ -38,11 +41,16 @@ class Pickup { this.fromTime, this.toTime, this.fcsID, - this.shipperName, - this.shipperPhoneNumber, this.status, this.packages = const [], - this.photoUrls = const []}); + this.photoUrls = const [], + this.completeRemark, + this.customerRemark, + this.pickupNumber, + this.rescheduleRemark, + this.zoneID, + this.zoneName, + this.pickupAddress}); @override bool operator ==(Object other) => other is Pickup && other.id == id; @@ -50,26 +58,63 @@ class Pickup { @override int get hashCode => id.hashCode; - bool isChangedForEdit(Pickup Pickup) { - return Pickup.userID != this.userID || - Pickup.fcsID != this.fcsID || - Pickup.packages != this.packages; + bool isChangedForEdit(Pickup pickup) { + return pickup.completeRemark != this.completeRemark; } factory Pickup.fromMap(Map map, String id) { var _pickupDate = (map['pickup_date'] as Timestamp); + var _fromTime = map['pickup_time_from'] == null + ? null + : (map['pickup_time_from'] as Timestamp); + var _toTime = map['pickup_time_to'] == null + ? null + : (map['pickup_time_to'] as Timestamp); + + var da = map['pickup_address']; + var _da = da != null ? DeliveryAddress.fromMap(da, da["id"]) : null; + + List _photoUrls = + map['photo_urls'] == null ? [] : List.from(map['photo_urls']); + return Pickup( id: id, - pickupDate: _pickupDate.toDate(), - fromTime: map['from_time'], - toTime: map['to_time'], - staffId: map['staff_id'], - staffName: map['staff_name'], - staffPhoneNumber: map['staff_phone_number'], - userID: map['user_id'], - userName: map['user_name'], - userPhoneNumber: map['user_phone_number'], - status: map['status']); + pickupDate: _pickupDate.toDate().toLocal(), + pickupNumber: map['pickup_number'], + pickupAddress: _da, + fromTime: _fromTime?.toDate().toLocal() ?? null, + toTime: _toTime?.toDate().toLocal() ?? null, + staffId: map['pickup_staff_id'], + staffName: map['pickup_staff_name'], + customerID: map['customer_id'], + customerName: map['customer_name'], + customerRemark: map['customer_remark'] ?? "", + completeRemark: map['complete_remark'] ?? "", + rescheduleRemark: map['reschedule_remark'], + zoneID: map['zone_id'], + zoneName: map['zone_name'], + status: map['status'], + photoUrls: _photoUrls); + } + + factory Pickup.fromJson(Map json) { + return Pickup( + id: json['id'], + pickupDate: DateTime.parse(json['pickup_date']), + pickupNumber: json['pickup_number'], + fromTime: DateTime.parse(json['pickup_date']), + toTime: DateTime.parse(json['pickup_date']), + staffId: json['pickup_staff_id'], + staffName: json['pickup_staff_name'], + customerID: json['customer_id'], + customerName: json['customer_name'], + customerRemark: json['customer_remark'] ?? "", + completeRemark: json['complete_remark'] ?? "", + rescheduleRemark: json['reschedule_remark'], + zoneID: json['zone_id'], + zoneName: json['zone_name'], + status: json['status'], + ); } Map toMap() { @@ -81,9 +126,8 @@ class Pickup { 'staff_id': staffId, 'staff_name': staffName, "staff_phone_number": staffPhoneNumber, - 'user_id': userID, - 'user_name': userName, - 'user_phone_number': userPhoneNumber, + 'user_id': customerID, + 'user_name': customerName, 'status': status, }; } @@ -92,7 +136,7 @@ class Pickup { return { "id": id, "complete_remark": completeRemark, - 'photo_urls': photoUrls ?? [], + 'photo_urls': photoUrls, }; } diff --git a/lib/helpers/firebase_helper.dart b/lib/helpers/firebase_helper.dart index 4014aa8..0184b69 100644 --- a/lib/helpers/firebase_helper.dart +++ b/lib/helpers/firebase_helper.dart @@ -44,7 +44,7 @@ Future uploadStorage(String path, File? file, } Reference ref = FirebaseStorage.instance.ref().child('$path/$fileName'); UploadTask uploadTask = ref.putFile(file); - await uploadTask.resume(); + await uploadTask; String downloadUrl = await ref.getDownloadURL(); return downloadUrl; // StorageReference storageReference = diff --git a/lib/main-dev.dart b/lib/main-dev.dart index fe11f38..98c7d72 100644 --- a/lib/main-dev.dart +++ b/lib/main-dev.dart @@ -15,7 +15,7 @@ Future main() async { Config( flavor: Flavor.DEV, color: Colors.blue, - apiURL: "https://asia-northeast1-fcs-dev1.cloudfunctions.net/API", + apiURL: "https://asia-northeast1-fcs-dev1.cloudfunctions.net/API12", reportURL: "http://petrok.mokkon.com:8091", reportProjectID: "fcs-dev", bucketName: "gs://fcs-dev1-us", diff --git a/lib/pages/customer/customer_editor.dart b/lib/pages/customer/customer_editor.dart index c85a44f..c810830 100644 --- a/lib/pages/customer/customer_editor.dart +++ b/lib/pages/customer/customer_editor.dart @@ -1,6 +1,8 @@ import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/chat/message_detail.dart'; +import 'package:fcs/pages/chat/model/message_model.dart'; import 'package:fcs/pages/customer/model/customer_model.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/display_text.dart'; @@ -51,6 +53,14 @@ class _CustomerEditorState extends State { color: enabled ? primaryColor : Colors.grey, callBack: () => _enable(!enabled), ); + + final chatBox = LocalButton( + textKey: "customer.chat.btn", + iconData: Icons.chat, + color: primaryColor, + callBack: () => _gotoChat(), + ); + return LocalProgress( inAsyncCall: _isLoading, child: SafeArea( @@ -100,6 +110,7 @@ class _CustomerEditorState extends State { context, "customer.invitation.request.confirm"), callack: _add) : Container(), + chatBox, widget.customer!.joined || widget.customer!.disabled ? enableBox : Container() @@ -149,4 +160,25 @@ class _CustomerEditorState extends State { }); } } + + _gotoChat() { + MessageModel messageModel = + Provider.of(context, listen: false); + messageModel.initQuery(widget.customer!.id); + Navigator.of(context) + .push(CupertinoPageRoute( + builder: (context) => MessageDetail( + receiverID: widget.customer!.id, + receiverName: widget.customer!.name, + messageModel: messageModel, + ))) + .then((value) { + if (widget.customer!.fcsUnseenCount > 0) { + messageModel.seenMessages(widget.customer!.id ?? "", false); + } + }); + if (widget.customer!.fcsUnseenCount > 0) { + messageModel.seenMessages(widget.customer!.id ?? "", false); + } + } } diff --git a/lib/pages/package/model/package_model.dart b/lib/pages/package/model/package_model.dart index a826954..33236fa 100644 --- a/lib/pages/package/model/package_model.dart +++ b/lib/pages/package/model/package_model.dart @@ -271,9 +271,7 @@ class PackageModel extends BaseModel { package.photoUrls = package.photoUrls == null ? [] : package.photoUrls; String path = Path.join(pkg_files_path); List urls = await uploadFiles(path, files); - urls.forEach((url) { - package.photoUrls.add(url); - }); + package.photoUrls =urls; } try { diff --git a/lib/pages/pickup/model/pickup_model.dart b/lib/pages/pickup/model/pickup_model.dart index 076b41e..8996319 100644 --- a/lib/pages/pickup/model/pickup_model.dart +++ b/lib/pages/pickup/model/pickup_model.dart @@ -1,13 +1,10 @@ import 'dart:async'; import 'dart:io'; -import 'dart:math'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/data/services/services.dart'; import 'package:fcs/domain/constants.dart'; -import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/pickup.dart'; -import 'package:fcs/domain/entities/processing.dart'; import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:fcs/pagination/paginator_listener.dart'; @@ -17,22 +14,7 @@ import 'package:path/path.dart' as Path; class PickupModel extends BaseModel { final log = Logger('PickupModel'); - StreamSubscription? listener; - - PaginatorListener? pickups; - - int _menuSelectedIndex = 1; - - set menuSelectedIndex(int index) { - _menuSelectedIndex = index; - - // _loadPackages(_menuSelectedIndex == 2); - // _loadCustomerPackages(_menuSelectedIndex == 2); - - notifyListeners(); - } - - int get menuSelectedIndex => _menuSelectedIndex; + PaginatorListener? pickups; void initUser(user) { super.initUser(user); @@ -44,13 +26,34 @@ class PickupModel extends BaseModel { } } - Future _initData() async {} + Future _initData() async { + logout(); + pickups = PaginatorListener((data, id) => Pickup.fromMap(data, id), + onChange: () { + notifyListeners(); + }, rowPerLoad: 30, insertNewByListener: true); + _loadPickups(); + } @override logout() async { - if (listener != null) await listener!.cancel(); if (pickups != null) pickups!.close(); - // pickups = []; + } + + Future _loadPickups() async { + if (user == null) return; + String path = "/$pickup_collection"; + + try { + Query listenerQuery = FirebaseFirestore.instance.collection(path); + Query pageQuery = FirebaseFirestore.instance + .collection(path) + .orderBy("update_time", descending: true); + + pickups!.refresh(listeningQuery: listenerQuery, pageQuery: pageQuery); + } catch (e) { + log.warning("Error!! $e"); + } } Future complete( @@ -82,4 +85,27 @@ class PickupModel extends BaseModel { } return deleteStorageFromUrls(deletedUrls); } + + Future getPickup(String id) async { + if (user == null) return null; + String path = "/$pickup_collection"; + try { + DocumentSnapshot snap = + await FirebaseFirestore.instance.collection("$path").doc(id).get(); + if (snap.exists) { + var package = + Pickup.fromMap(snap.data() as Map, snap.id); + return package; + } + } catch (e) { + log.warning("Error!! $e"); + } + return null; + } + + Future> searchPickup(String term) async { + Future> pickups = + Services.instance.pickupService.searchPickup(term); + return pickups; + } } diff --git a/lib/pages/pickup/pickup_editor.dart b/lib/pages/pickup/pickup_editor.dart index c3641b0..281f6ab 100644 --- a/lib/pages/pickup/pickup_editor.dart +++ b/lib/pages/pickup/pickup_editor.dart @@ -1,15 +1,15 @@ import 'package:fcs/domain/entities/market.dart'; import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/entities/pickup.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/market/market_editor.dart'; import 'package:fcs/pages/market/model/market_model.dart'; -import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/package/tracking_id_page.dart'; import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/user_search/user_serach.dart'; +import 'package:fcs/pages/pickup/model/pickup_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/input_text.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/multi_img_controller.dart'; @@ -22,49 +22,56 @@ import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; class PickupEditor extends StatefulWidget { - final Package? package; - PickupEditor({this.package}); + final Pickup? pickup; + + const PickupEditor({Key? key, this.pickup}) : super(key: key); @override _PickupEditorState createState() => _PickupEditorState(); } class _PickupEditorState extends State { - TextEditingController _remarkCtl = new TextEditingController(); - TextEditingController _descCtl = new TextEditingController(); + var dateFormatter = new DateFormat('dd MMM yyyy'); + var timeFormatter = new DateFormat('h:mm a'); - Package? _package; - User? _user; + TextEditingController _remarkCtl = new TextEditingController(); + MultiImgController multiImgController = MultiImgController(); + + Pickup? _pickup; bool _isLoading = false; @override void initState() { super.initState(); - _package = widget.package; - selectedMarket = _package!.market ?? ""; - _descCtl.text = _package!.desc ?? ""; - _remarkCtl.text = _package!.remark ?? ""; - multiImgController.setImageUrls = _package!.photoUrls; - _user = User( - fcsID: _package!.fcsID ?? "", - name: _package!.userName ?? "", - phoneNumber: _package!.phoneNumber ?? ""); + _pickup = widget.pickup; + multiImgController.setImageUrls = _pickup!.photoUrls; } - final DateFormat dateFormat = DateFormat("d MMM yyyy"); - - bool isNew = false; - MultiImgController multiImgController = MultiImgController(); - @override Widget build(BuildContext context) { + final pickupNumberBox = DisplayText( + text: _pickup!.pickupNumber ?? "", + labelTextKey: "pickup.pickup_number", + iconData: SimpleLineIcons.direction, + ); + + final pickupDateBox = DisplayText( + text: _pickup!.pickupDate == null + ? "" + : dateFormatter.format(_pickup!.pickupDate!), + labelTextKey: "pickup.date", + iconData: Icons.date_range, + ); + var timeBox = Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( width: 150, child: DisplayText( - text: '9:00 AM', + text: _pickup!.fromTime == null + ? "" + : timeFormatter.format(_pickup!.fromTime!), labelTextKey: "pickup.from_time", iconData: MaterialCommunityIcons.clock_start, ), @@ -72,42 +79,46 @@ class _PickupEditorState extends State { Container( width: 150, child: DisplayText( - text: '12:00 AM', - labelTextKey: "pickup.to_time", - iconData: MaterialCommunityIcons.clock_end), + text: _pickup!.toTime == null + ? "" + : timeFormatter.format(_pickup!.toTime!), + labelTextKey: "pickup.to_time", + iconData: MaterialCommunityIcons.clock_end, + ), ) ], ); - final pickupDateBox = DisplayText( - text: "12-05-2021", - labelTextKey: "pickup.date", - iconData: Icons.date_range, + final customerBox = DisplayText( + text: _pickup!.customerName ?? "", + labelTextKey: "pickup.customer", + iconData: Icons.perm_identity, ); - final pickupNumberBox = DisplayText( - text: "210502-ASDFRE", - labelTextKey: "pickup.pickup_number", - iconData: SimpleLineIcons.direction, + final staffNameBox = DisplayText( + text: _pickup!.staffName ?? "", + labelTextKey: "pickup.staff.name", + iconData: Icons.perm_identity, ); + + final deliveryAddressBox = DefaultDeliveryAddress( + deliveryAddress: _pickup!.pickupAddress, + labelKey: "pickup.delivery.address", + onTap: null); + final completeProcessingBtn = fcsButton( context, getLocalString(context, 'pickup.edit.complete.btn'), callack: _confirmComplete, ); - final staffNameBox = DisplayText( - text: _package != null ? _package!.userName : "", - labelTextKey: "pickup.staff.name", - iconData: Icons.perm_identity, - ); - final remarkBox = InputText( - labelTextKey: 'pickup.remark', + final completeRemarkBox = InputText( + labelTextKey: 'pickup.complete.remark', iconData: Entypo.new_message, controller: _remarkCtl); final statusBox = DisplayText( - text: _package != null ? _package!.status : "", + text: _pickup != null ? _pickup!.status : "", labelTextKey: "pickup.status", iconData: Icons.av_timer, ); @@ -117,6 +128,7 @@ class _PickupEditorState extends State { controller: multiImgController, title: "Receipt File", ); + return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -150,11 +162,13 @@ class _PickupEditorState extends State { pickupNumberBox, pickupDateBox, timeBox, + customerBox, staffNameBox, statusBox, - remarkBox, + deliveryAddressBox, + completeRemarkBox, img, - _package!.status == 'packed' + _pickup!.status == "confirmed" || _pickup!.status == "rescheduled" ? completeProcessingBtn : Container(), SizedBox( @@ -244,22 +258,15 @@ class _PickupEditorState extends State { } _completePickup() async { - if (_user!.fcsID == null || _user!.fcsID == "") { - showMsgDialog(context, "Error", "Expected FCS-ID"); - return; - } setState(() { _isLoading = true; }); - PackageModel packageModel = - Provider.of(context, listen: false); + try { - // _package!.fcsID = _user!.fcsID; - // _package!.desc = _descCtl.text; - // _package!.remark = _remarkCtl.text; - // _package!.market = selectedMarket!; - // await packageModel.updateProcessing(_package!, - // multiImgController.getAddedFile, multiImgController.getDeletedUrl); + _pickup?.completeRemark = _remarkCtl.text; + await context.read().complete(_pickup!, + multiImgController.getAddedFile, multiImgController.getDeletedUrl); + Navigator.pop(context); } catch (e) { showMsgDialog(context, "Error", e.toString()); @@ -277,23 +284,10 @@ class _PickupEditorState extends State { } isDataChanged() { - if (isNew) { - return _user!.fcsID != "" || - selectedMarket != null || - _descCtl.text != "" || - _remarkCtl.text != "" || - multiImgController.getAddedFile.isNotEmpty; - } else { - var _package = Package( - trackingID: widget.package!.trackingID, - fcsID: _user!.fcsID, - market: selectedMarket!, - desc: _descCtl.text, - remark: _remarkCtl.text, - photoUrls: widget.package!.photoUrls); - return widget.package!.isChangedForEditProcessing(_package) || - multiImgController.getAddedFile.isNotEmpty || - multiImgController.getDeletedUrl.isNotEmpty; - } + var _pickup = Pickup( + completeRemark: _remarkCtl.text, photoUrls: widget.pickup!.photoUrls); + return widget.pickup!.isChangedForEdit(_pickup) || + multiImgController.getAddedFile.isNotEmpty || + multiImgController.getDeletedUrl.isNotEmpty; } } diff --git a/lib/pages/pickup/pickup_info.dart b/lib/pages/pickup/pickup_info.dart index 04e678f..aca18aa 100644 --- a/lib/pages/pickup/pickup_info.dart +++ b/lib/pages/pickup/pickup_info.dart @@ -1,15 +1,14 @@ -import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/entities/pickup.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/package/model/package_model.dart'; +import 'package:fcs/pages/pickup/model/pickup_model.dart'; import 'package:fcs/pages/pickup/pickup_editor.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/local_text.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_vector_icons/flutter_vector_icons.dart'; @@ -19,8 +18,8 @@ import 'package:provider/provider.dart'; final DateFormat dateFormat = DateFormat("d MMM yyyy"); class PickupInfo extends StatefulWidget { - final Package? package; - PickupInfo({this.package}); + final Pickup pickup; + const PickupInfo({Key? key, required this.pickup}) : super(key: key); @override _PickupInfoState createState() => _PickupInfoState(); @@ -28,21 +27,22 @@ class PickupInfo extends StatefulWidget { class _PickupInfoState extends State { var dateFormatter = new DateFormat('dd MMM yyyy'); - Package? _package; + var timeFormatter = new DateFormat('h:mm a'); + + Pickup? _pickup; bool _isLoading = false; MultiImgController multiImgController = MultiImgController(); @override void initState() { super.initState(); - initPackage(widget.package!); + initPackage(widget.pickup); } - initPackage(Package? package) { - if (package == null) return; + initPackage(Pickup pickup) { setState(() { - _package = package; - multiImgController.setImageUrls = package.photoUrls; + _pickup = pickup; + multiImgController.setImageUrls = pickup.photoUrls; }); } @@ -54,25 +54,64 @@ class _PickupInfoState extends State { @override Widget build(BuildContext context) { final pickupNumberBox = DisplayText( - text: _package != null ? '210502-ASDFRE' : '210502-ASDFRE', + text: _pickup!.pickupNumber ?? "", labelTextKey: "pickup.pickup_number", iconData: SimpleLineIcons.direction, ); + final pickupDateBox = DisplayText( + text: _pickup!.pickupDate == null + ? "" + : dateFormatter.format(_pickup!.pickupDate!), + labelTextKey: "pickup.date", + iconData: Icons.date_range, + ); + + var timeBox = Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 150, + child: DisplayText( + text: _pickup!.fromTime == null + ? "" + : timeFormatter.format(_pickup!.fromTime!), + labelTextKey: "pickup.from_time", + iconData: MaterialCommunityIcons.clock_start, + ), + ), + Container( + width: 150, + child: DisplayText( + text: _pickup!.toTime == null + ? "" + : timeFormatter.format(_pickup!.toTime!), + labelTextKey: "pickup.to_time", + iconData: MaterialCommunityIcons.clock_end, + ), + ) + ], + ); + + final customerBox = DisplayText( + text: _pickup!.customerName ?? "", + labelTextKey: "pickup.customer", + iconData: Icons.perm_identity, + ); + final staffNameBox = DisplayText( - text: _package != null ? _package!.userName : "", + text: _pickup!.staffName ?? "", labelTextKey: "pickup.staff.name", iconData: Icons.perm_identity, ); - final remarkBox = DisplayText( - text: _package != null ? _package!.remark : "-", - labelTextKey: "pickup.remark", - iconData: Entypo.new_message, - ); + final deliveryAddressBox = DefaultDeliveryAddress( + deliveryAddress: _pickup!.pickupAddress, + labelKey: "pickup.delivery.address", + onTap: null); final statusBox = DisplayText( - text: _package != null ? _package!.status : "", + text: _pickup != null ? _pickup!.status : "", labelTextKey: "pickup.status", iconData: Icons.av_timer, ); @@ -83,26 +122,22 @@ class _PickupInfoState extends State { title: "Receipt File", ); - var timeBox = Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 150, - child: DisplayText( - text: '9:00 AM', - labelTextKey: "pickup.from_time", - iconData: MaterialCommunityIcons.clock_start, - ), - ), - Container( - width: 150, - child: DisplayText( - text: '12:00 AM', - labelTextKey: "pickup.to_time", - iconData: MaterialCommunityIcons.clock_end, - ), - ) - ], + final completeRemarkBox = DisplayText( + text: _pickup!.completeRemark ?? "", + labelTextKey: "pickup.complete.remark", + iconData: Entypo.new_message, + ); + + final rescheduleRemarkBox = DisplayText( + text: _pickup!.rescheduleRemark ?? "", + labelTextKey: "pickup.reschedul.remark", + iconData: Entypo.new_message, + ); + + final customerRemarkBox = DisplayText( + text: _pickup!.customerRemark ?? "", + labelTextKey: "pickup.customer.remark", + iconData: Entypo.new_message, ); final continueBtn = fcsButton( @@ -111,12 +146,6 @@ class _PickupInfoState extends State { callack: _gotoEditor, ); - final pickupDateBox = DisplayText( - text: "12-05-2021", - labelTextKey: "pickup.date", - iconData: Icons.date_range, - ); - return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -145,11 +174,27 @@ class _PickupInfoState extends State { pickupNumberBox, pickupDateBox, timeBox, + customerBox, + _pickup!.completeRemark == null || + _pickup!.completeRemark == "" + ? Container() + : customerRemarkBox, staffNameBox, - remarkBox, statusBox, - _package!.photoUrls.length == 0 ? Container() : img, - _package!.status == "packed" ? continueBtn : Container(), + _pickup!.rescheduleRemark == null || + _pickup!.rescheduleRemark == "" + ? Container() + : rescheduleRemarkBox, + _pickup!.completeRemark == null || + _pickup!.completeRemark == "" + ? Container() + : completeRemarkBox, + deliveryAddressBox, + _pickup!.photoUrls.length == 0 ? Container() : img, + _pickup!.status == "confirmed" || + _pickup!.status == "rescheduled" + ? continueBtn + : Container(), SizedBox( height: 20, ) @@ -162,41 +207,16 @@ class _PickupInfoState extends State { ); } - _delete() { - showConfirmDialog(context, "pickup.delete.confirm", _deletePackage); - } - - _deletePackage() async { - setState(() { - _isLoading = true; - }); - PackageModel packageModel = - Provider.of(context, listen: false); - try { - await packageModel.deleteProcessing(_package!); - Navigator.pop(context, true); - } catch (e) { - showMsgDialog(context, "Error", e.toString()); - } finally { - setState(() { - _isLoading = false; - }); - } - } - _gotoEditor() async { bool? deleted = await Navigator.push( context, CupertinoPageRoute( - builder: (context) => PickupEditor( - package: widget.package, - ))); + builder: (context) => PickupEditor(pickup: widget.pickup))); if (deleted ?? false) { Navigator.pop(context); } else { - PackageModel packageModel = - Provider.of(context, listen: false); - Package? p = await packageModel.getPackage(_package!.id!); + Pickup? p = await context.read().getPickup(_pickup!.id!); + if (p == null) return; initPackage(p); } } diff --git a/lib/pages/pickup/pickup_list.dart b/lib/pages/pickup/pickup_list.dart index bba3551..74f7e35 100644 --- a/lib/pages/pickup/pickup_list.dart +++ b/lib/pages/pickup/pickup_list.dart @@ -1,10 +1,12 @@ import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/entities/pickup.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/package_search/package_serach.dart'; import 'package:fcs/pages/pickup/pickup_editor.dart'; import 'package:fcs/pages/pickup/pickup_info.dart'; import 'package:fcs/pages/pickup/pickup_list_row.dart'; +import 'package:fcs/pages/pickup_search/pickup_serach.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pagination/paginator_listview.dart'; @@ -12,6 +14,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'model/pickup_model.dart'; + class PickupList extends StatefulWidget { @override _PickupListState createState() => _PickupListState(); @@ -32,8 +36,8 @@ class _PickupListState extends State { @override Widget build(BuildContext context) { - var packageModel = Provider.of(context); - var packages = packageModel.activePackages; + var pickupModel = Provider.of(context); + var pickups = pickupModel.pickups; return LocalProgress( inAsyncCall: _isLoading, @@ -58,29 +62,25 @@ class _PickupListState extends State { color: Colors.white, ), iconSize: 30, - onPressed: () => searchPackage(context, - callbackPackageSelect: _searchCallback), + onPressed: () => searchPickup(context, + callbackPickupSelect: _searchCallback), ), ], ), - body: PaginatorListView( - paginatorListener: packages!, - rowBuilder: (p) => PickupListRow( - key: ValueKey(p.id), - package: p, - ), + body: PaginatorListView( + paginatorListener: pickups!, + rowBuilder: (p) => PickupListRow(key: ValueKey(p.id), pickup: p), color: primaryColor, )), ); } - _searchCallback(Package package) async { - var packageModel = Provider.of(context, listen: false); - Package? _package = await packageModel.getPackage(package.id!); - if (_package == null) return; + _searchCallback(Pickup pickup) async { + Pickup? _pickup = await context.read().getPickup(pickup.id!); + if (_pickup == null) return; Navigator.push( context, - CupertinoPageRoute(builder: (context) => PickupInfo(package: _package)), + CupertinoPageRoute(builder: (context) => PickupInfo(pickup: _pickup)), ); } } diff --git a/lib/pages/pickup/pickup_list_row.dart b/lib/pages/pickup/pickup_list_row.dart index 36d06e4..b66a824 100644 --- a/lib/pages/pickup/pickup_list_row.dart +++ b/lib/pages/pickup/pickup_list_row.dart @@ -1,4 +1,4 @@ -import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/entities/pickup.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/pickup/pickup_info.dart'; @@ -7,29 +7,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:intl/intl.dart'; -typedef CallbackPackageSelect(Package package); +typedef CallbackPickupSelect(Pickup pickup); class PickupListRow extends StatelessWidget { - final Package? package; - final CallbackPackageSelect? callbackPackageSelect; + final Pickup? pickup; + final CallbackPickupSelect? callbackPickupSelect; final double dotSize = 15.0; final DateFormat dateFormat = new DateFormat("dd MMM yyyy"); - PickupListRow({Key? key, this.package, this.callbackPackageSelect}) + PickupListRow({Key? key, this.pickup, this.callbackPickupSelect}) : super(key: key); @override Widget build(BuildContext context) { return InkWell( onTap: () { - if (callbackPackageSelect != null) { - callbackPackageSelect!(package!); + if (callbackPickupSelect != null) { + callbackPickupSelect!(pickup!); return; } Navigator.push( context, - CupertinoPageRoute( - builder: (context) => PickupInfo(package: package)), + CupertinoPageRoute(builder: (context) => PickupInfo(pickup: pickup!)), ); }, child: Container( @@ -56,19 +55,21 @@ class PickupListRow extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 8.0), child: new Text( - package!.id == null ? '' : package!.trackingID!, + pickup?.pickupNumber ?? '', style: new TextStyle( fontSize: 15.0, color: Colors.black), ), ), Padding( - padding: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.only(left: 10.0, top: 10), child: new Text( - package!.market == null ? '' : package!.market!, + pickup!.pickupDate != null + ? dateFormat.format(pickup!.pickupDate!) + : "", style: new TextStyle( - fontSize: 15.0, color: Colors.black), + fontSize: 15.0, color: Colors.grey), ), - ), + ) ], ), ), @@ -78,19 +79,7 @@ class PickupListRow extends StatelessWidget { ), Column( children: [ - Padding( - padding: const EdgeInsets.all(3.0), - child: getStatus(package!.status ?? ""), - ), - Padding( - padding: const EdgeInsets.all(0), - child: new Text( - package!.currentStatusDate != null - ? dateFormat.format(package!.currentStatusDate!) - : '', - style: new TextStyle(fontSize: 15.0, color: Colors.grey), - ), - ), + getStatus(pickup!.status ?? ""), ], ) ], diff --git a/lib/pages/pickup_search/pickup_serach.dart b/lib/pages/pickup_search/pickup_serach.dart new file mode 100644 index 0000000..76ea3ab --- /dev/null +++ b/lib/pages/pickup_search/pickup_serach.dart @@ -0,0 +1,162 @@ +import 'package:fcs/domain/entities/pickup.dart'; +import 'package:fcs/helpers/theme.dart'; + +import 'package:fcs/pages/pickup/model/pickup_model.dart'; +import 'package:fcs/pages/pickup/pickup_list_row.dart'; +import 'package:fcs/pages/widgets/barcode_scanner.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_vector_icons/flutter_vector_icons.dart'; +import 'package:provider/provider.dart'; + +Future searchPickup(BuildContext context, + {CallbackPickupSelect? callbackPickupSelect}) async => + await showSearch( + context: context, + delegate: + PackageSearchDelegate(callbackPickupSelect: callbackPickupSelect), + ); + +class PackageSearchDelegate extends SearchDelegate { + final CallbackPickupSelect? callbackPickupSelect; + + PackageSearchDelegate({this.callbackPickupSelect}); + + @override + String get searchFieldLabel => 'Search by Pickup Number'; + + @override + ThemeData appBarTheme(BuildContext context) { + final ThemeData theme = Theme.of(context); + return theme.copyWith( + appBarTheme: AppBarTheme(color: primaryColor), + inputDecorationTheme: InputDecorationTheme( + border: InputBorder.none, + hintStyle: TextStyle( + color: theme.primaryTextTheme.caption?.color, fontSize: 14)), + textTheme: TextTheme( + headline1: TextStyle( + color: theme.primaryTextTheme.headline1?.color, + fontSize: 16, + backgroundColor: primaryColor)), + primaryColor: primaryColor, + ); + } + + @override + List buildActions(BuildContext context) { + return [ + IconButton( + icon: Icon(MaterialCommunityIcons.barcode_scan, + size: 30, color: Colors.white), + onPressed: () => _scan(context), + ), + IconButton( + icon: Icon(Icons.clear), + onPressed: () => query = '', + ), + ]; + } + + @override + Widget buildLeading(BuildContext context) { + return IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () => close(context, new Pickup()), + ); + } + + @override + Widget buildResults(BuildContext context) { + final pickupModel = Provider.of(context); + return FutureBuilder( + future: pickupModel.searchPickup(query), + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + if (snapshot.data!.length == 0) { + return Container( + child: Center( + child: Text( + "No result found", + textAlign: TextAlign.center, + ), + ), + ); + } + return Container( + padding: EdgeInsets.only(top: 15), + child: ListView( + children: snapshot.data!.map((e) { + return PickupListRow( + pickup: e, + callbackPickupSelect: callbackPickupSelect, + ); + }).toList(), + ), + ); + } else if (snapshot.hasError) { + return Container( + child: Center( + child: Text( + '${snapshot.error}', + textAlign: TextAlign.center, + ), + ), + ); + } else { + return Container( + child: Center( + child: CircularProgressIndicator( + valueColor: + new AlwaysStoppedAnimation(primaryColor)), + ), + ); + } + }); + } + + @override + Widget buildSuggestions(BuildContext context) { + return Container( + child: Center( + child: Opacity( + opacity: 0.2, + child: Icon(SimpleLineIcons.direction, size: 200, color: primaryColor)), + ), + ); + } + + _scan(BuildContext context) async { + // PermissionStatus permission = + // await PermissionHandler().checkPermissionStatus(PermissionGroup.camera); + // if (permission != PermissionStatus.granted) { + // Map permissions = + // await PermissionHandler() + // .requestPermissions([PermissionGroup.camera]); + // if (permissions[PermissionGroup.camera] != PermissionStatus.granted) { + // showMsgDialog(context, "Error", "Camera permission is not granted"); + // return null; + // } + // } + + try { + // PickedFile pickedFile = + // await ImagePicker().getImage(source: ImageSource.camera); + // FirebaseVisionImage visionImage = + // FirebaseVisionImage.fromFile(File(pickedFile.path)); + // final BarcodeDetector barcodeDetector = + // FirebaseVision.instance.barcodeDetector(); + // final List barcodes = + // await barcodeDetector.detectInImage(visionImage); + // Barcode bc = barcodes.firstWhere((element) => true); + // String barcode; + // if (bc != null) barcode = bc.rawValue; + String? barcode = await scanBarcode(); + if (barcode != null) { + query = barcode; + showResults(context); + } + } catch (e) { + print('error: $e'); + } + } +} diff --git a/lib/pages/rates/cargo_editor.dart b/lib/pages/rates/cargo_editor.dart index 2c25d86..a0728fe 100644 --- a/lib/pages/rates/cargo_editor.dart +++ b/lib/pages/rates/cargo_editor.dart @@ -73,10 +73,12 @@ class _CargoEditorState extends State { backgroundColor: primaryColor, title: Text(AppTranslations.of(context)!.text("cargo.form.title")), actions: [ - IconButton( - icon: Icon(Icons.delete), - onPressed: _delete, - ) + _isNew + ? Container() + : IconButton( + icon: Icon(Icons.delete), + onPressed: _delete, + ) ], ), body: Container( diff --git a/lib/pages/rates/custom_editor.dart b/lib/pages/rates/custom_editor.dart index bec5ddb..5e8a9ba 100644 --- a/lib/pages/rates/custom_editor.dart +++ b/lib/pages/rates/custom_editor.dart @@ -33,7 +33,7 @@ class _CustomEditorState extends State { super.initState(); if (widget.custom != null) { _custom = widget.custom!; - _productController.text = _custom.name??""; + _productController.text = _custom.name ?? ""; _feeController.text = _custom.customDutyFee.toStringAsFixed(2); _shipmentRateController.text = _custom.rate == null ? "" : _custom.rate.toStringAsFixed(2); @@ -85,10 +85,12 @@ class _CustomEditorState extends State { title: Text(AppTranslations.of(context)!.text("rate.custom.form.title")), actions: [ - IconButton( - icon: Icon(Icons.delete), - onPressed: _delete, - ) + _isNew + ? Container() + : IconButton( + icon: Icon(Icons.delete), + onPressed: _delete, + ) ], ), body: Container( diff --git a/lib/pages/rates/discount_by_weight_editor.dart b/lib/pages/rates/discount_by_weight_editor.dart index 11096fd..f910a51 100644 --- a/lib/pages/rates/discount_by_weight_editor.dart +++ b/lib/pages/rates/discount_by_weight_editor.dart @@ -75,10 +75,12 @@ class _DiscountByWeightEditorState extends State { backgroundColor: primaryColor, title: Text(AppTranslations.of(context)!.text("discount.new")), actions: [ - IconButton( - icon: Icon(Icons.delete), - onPressed: _delete, - ) + _isNew + ? Container() + : IconButton( + icon: Icon(Icons.delete), + onPressed: _delete, + ) ], ), body: Container(