diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index af2569e..08d0ae9 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -92,6 +92,8 @@ "Home Start ================================================================":"", "home.invitation.request.msg":"We are working on your invitation request!", + "home.search":"Enter tracking number", + "home.search.btn":"Search", "Home End ================================================================":"", "Invite Start ================================================================":"", @@ -177,6 +179,8 @@ "package.rate":"Rate", "package.weight":"Weight", "package.amount":"Amount", + "package.popupmenu.active":"Active Packages", + "package.popupmenu.delivered":"Delivered Packages", "Package End ================================================================":"", "Market Start ================================================================":"", @@ -380,27 +384,32 @@ "delivery_addresses End ================================================================":"", "Receiving Start ================================================================":"", - "receiving.title":"Receiving", + "receiving.title":"Receivings", + "receiving.info":"Receiving", "receiving.new":"New Receiving", + "receiving.update":"Update Reveiving", "receiving.tracking.id":"Tracking ID", "receiving.remark":"Remark", "receiving.fcs.id":"FCS ID", "receiving.name":"Customer Name", "receiving.phone":"Phone Number", "receiving.create_btn":"Complete receiving", + "receiving.update_btn":"Update reveiving", "receiving.delete.confirm":"Delete this receiving?", "Receiving End ================================================================":"", "Processing Start ================================================================":"", "processing.title":"Processing", - "processing.info.title":"Package", + "processing.info.title":"Processing", "processing.tracking.id":"Tracking ID", "processing.name":"Customer Name", + "processing.phone":"Phone Number", + "processing.fcs.id":"FCS ID", "processing.market":"Market", "processing.status":"Status", "processing.desc":"Description", "processing.remark":"Remark", - "processing.edit.title":"Edit Package", + "processing.edit.title":"Processing", "processing.delete.confirm":"Delete this package?", "processing.edit.sub_title":"Processing", "processing.edit.complete.btn":"Complete processing", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index e43679f..6219dbb 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -92,6 +92,8 @@ "Home Start ================================================================":"", "home.invitation.request.msg":"ဖိတ်ကြားမှု တောင်းဆိုသည်ကို လုပ်ဆောင်နေပါသည်!", + "home.search":"Tracking number ရိုက်ထဲ့ပါ", + "home.search.btn":"ရှာမည်", "Home End ================================================================":"", "Invite Start ================================================================":"", @@ -177,6 +179,8 @@ "package.rate":"Rate", "package.weight":"Weight", "package.amount":"Amount", + "package.popupmenu.active":"လာမည့် အထုပ်များ", + "package.popupmenu.delivered":"ပို့ပြီးသော အထုပ်များ", "Package End ================================================================":"", "Market Start ================================================================":"", @@ -195,7 +199,7 @@ "Payment Start ================================================================":"", "pm.title":"ငွေပေးချေစနစ်များ", - "pm.new":"ငွေပေးချေစနစ်အသစ်", + "pm.new":"ငွေပေးချေစနစ် အသစ်", "pm.update":"ငွေပေးချေစနစ် ပြင်ဆင်ခြင်း", "pm.btn":"ငွေပေးချေစနစ်များ", "pm.name":"အမည်", @@ -225,7 +229,7 @@ "Boxes Start ================================================================":"", "boxes.name":"သေတ္တာများ", "boxes.title":"Boxes", - "boxes.new":"သေတ္တာအသစ်", + "boxes.new":"သေတ္တာ အသစ်", "box.edit.title":"သေတ္တာ ပြင်ဆင်ခြင်း", "box.package.id":"Package ID", "box.package.desc":"Description", @@ -286,9 +290,9 @@ "FCS Shipment End ================================================================":"", "Shipment Start ================================================================":"", - "shipment": "Shipments", - "shipment.title": "SHIPMENTS", - "shipment.new": "New Shipment", + "shipment": "ပို့ဆောင်ခြင်းများ", + "shipment.title": "ပို့ဆောင်ခြင်းများ", + "shipment.new": "ပို့ဆောင်ခြင်း အသစ်", "shipment.edit.title": "PICKUP", "shipment.date": "Pickup Date", "shipment.location_time": "Pickup Location / Time", @@ -374,33 +378,38 @@ "delivery_address.phonenumber": "ဖုန်းနံပါတ်", "delivery_address.create": "ပြုလုပ်ရန်", "delivery_address.update": "ပြုပြင်ရန်", - "delivery_address.new_address":"လိပ်စာအသစ်", + "delivery_address.new_address":"လိပ်စာ အသစ်", "delivery_address.change_address": "လိပ်စာပြောင်းပါ", "delivery_address.delete.confirm":"ပို့ဆောင်ရမည့်လိပ်စာကို ဖျက်မလား?", "delivery_addresses End ================================================================":"", "Receiving Start ================================================================":"", "receiving.title":"လက်ခံခြင်းများ", - "receiving.new":"လက်ခံခြင်းအသစ်", + "receiving.info":"လက်ခံခြင်း", + "receiving.new":"လက်ခံခြင်း အသစ်", + "receiving.update":"လက်ခံခြင်း ပြင်ဆင်ခြင်း", "receiving.tracking.id":"Tracking ID", "receiving.remark":"မှတ်ချက်", "receiving.fcs.id":"FCS ID", "receiving.name":"နာမည်", "receiving.phone":"ဖုန်းနံပါတ်", "receiving.create_btn":"လက်ခံမည်", + "receiving.update_btn":"ပြင်ဆင်မည်", "receiving.delete.confirm":"လက်ခံခြင်းကို ဖျက်မလား?", "Receiving End ================================================================":"", "Processing Start ================================================================":"", - "processing.title":"မွမ်းမံခြင်းများ", - "processing.info.title":"အထုပ်", + "processing.title":"လုပ်ဆောင်ခြင်းများ", + "processing.info.title":"လုပ်ဆောင်ခြင်း", "processing.tracking.id":"Tracking ID", "processing.name":"နာမည်", + "processing.phone":"ဖုန်းနံပါတ်", + "processing.fcs.id":"FCS ID", "processing.market":"အွန်လိုင်စျေးဆိုင်", "processing.status":"အခြေအနေ", "processing.desc":"ဖော်ပြချက်", "processing.remark":"မှတ်ချက်", - "processing.edit.title":"အထုပ် ပြင်ဆင်ခြင်း", + "processing.edit.title":"လုပ်ဆောင်ခြင်း", "processing.delete.confirm":"အထုပ်ကို ဖျက်မလား?", "processing.edit.sub_title":"မွမ်းမံခြင်း", "processing.edit.complete.btn":"မွမ်းမံခြင်း ပြီးဆုံးသည်", diff --git a/lib/data/provider/package_data_provider.dart b/lib/data/provider/package_data_provider.dart index 9eec0c7..4ccf220 100644 --- a/lib/data/provider/package_data_provider.dart +++ b/lib/data/provider/package_data_provider.dart @@ -16,13 +16,28 @@ class PackageDataProvider { payload: {"packages": json, "fcs_id": fcsID}, token: await getToken()); } - Future createPackage(Package package) async { - return await requestAPI("/package", "POST", + Future createReceiving(Package package) async { + return await requestAPI("/receiving", "POST", payload: package.toJson(), token: await getToken()); } - Future deletePackage(Package package) async { - return await requestAPI("/package", "DELETE", + Future updateReceiving(Package package) async { + return await requestAPI("/receiving", "PUT", + payload: package.toJson(), token: await getToken()); + } + + Future deleteReceiving(Package package) async { + return await requestAPI("/receiving", "DELETE", + payload: {"id": package.id}, token: await getToken()); + } + + Future updateProcessing(Package package) async { + return await requestAPI("/processing", "PUT", + payload: package.toJson(), token: await getToken()); + } + + Future deleteProcessing(Package package) async { + return await requestAPI("/processing", "DELETE", payload: {"id": package.id}, token: await getToken()); } diff --git a/lib/data/services/package_imp.dart b/lib/data/services/package_imp.dart index 9e33dff..db8ae62 100644 --- a/lib/data/services/package_imp.dart +++ b/lib/data/services/package_imp.dart @@ -20,8 +20,13 @@ class PackageServiceImp implements PackageService { } @override - Future createPackage(Package package) { - return packageDataProvider.createPackage(package); + Future createReceiving(Package package) { + return packageDataProvider.createReceiving(package); + } + + @override + Future updateReceiving(Package package) { + return packageDataProvider.updateReceiving(package); } @override @@ -30,7 +35,17 @@ class PackageServiceImp implements PackageService { } @override - Future deletePackage(Package package) { - return packageDataProvider.deletePackage(package); + Future deleteReceiving(Package package) { + return packageDataProvider.deleteReceiving(package); + } + + @override + Future updateProcessing(Package package) { + return packageDataProvider.updateProcessing(package); + } + + @override + Future deleteProcessing(Package package) { + return packageDataProvider.deleteProcessing(package); } } diff --git a/lib/data/services/package_service.dart b/lib/data/services/package_service.dart index 93ac51d..c16dced 100644 --- a/lib/data/services/package_service.dart +++ b/lib/data/services/package_service.dart @@ -2,7 +2,10 @@ import 'package:fcs/domain/entities/package.dart'; abstract class PackageService { Future createPackages(List packages, String fcsID); - Future createPackage(Package package); - Future deletePackage(Package package); + Future createReceiving(Package package); + Future updateReceiving(Package package); + Future deleteReceiving(Package package); + Future updateProcessing(Package package); + Future deleteProcessing(Package package); Future> searchPackage(String term); } diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index 690fa9b..10f6be1 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -35,6 +35,7 @@ const package_shipped_status = "shipped"; const package_delivered_status = "delivered"; // Privileges +const privilege_sys_admin = "sa"; const privilege_admin = "admin"; const privilege_support = "sp"; const privilege_package = "pkg"; diff --git a/lib/domain/entities/user.dart b/lib/domain/entities/user.dart index 2c43357..d3f6eb0 100644 --- a/lib/domain/entities/user.dart +++ b/lib/domain/entities/user.dart @@ -3,6 +3,8 @@ import 'package:fcs/helpers/const.dart'; import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; +import '../constants.dart'; + DateFormat dayFormat = DateFormat("MMM dd yyyy"); DateFormat timeFormat = DateFormat("HH:mm"); final DateFormat dateFormat = DateFormat("d MMM yyyy"); @@ -123,36 +125,59 @@ class User { } bool hasSysAdmin() { - return privileges != null ? privileges.contains('sa') : false; + return _has(privilege_sys_admin); } bool hasAdmin() { - return privileges != null ? privileges.contains('admin') : false; + return _has(privilege_admin); } bool hasCustomers() { - return hasSysAdmin() || - hasAdmin() || - (privileges != null ? privileges.contains('c') : false); + return hasSysAdmin() || hasAdmin() || _has(privilege_customer); + } + + bool hasFcsShipments() { + return hasSysAdmin() || hasAdmin() || _has(privilege_fcs_shipment); } bool hasStaffs() { - return hasSysAdmin() || - hasAdmin() || - (privileges != null ? privileges.contains('s') : false); + return hasSysAdmin() || hasAdmin() || _has(privilege_staff); } bool hasSupport() { - return hasSysAdmin() || - hasAdmin() || - (privileges != null ? privileges.contains('sp') : false); + return hasSysAdmin() || hasAdmin() || _has(privilege_support); } bool hasPackages() { - return hasSysAdmin() || - hasAdmin() || - status == userStatusJoined || - (privileges != null ? privileges.contains('p') : false); + return hasSysAdmin() || hasAdmin() || _has(privilege_package); + } + + bool hasReceiving() { + return hasSysAdmin() || hasAdmin() || _has(privilege_receiving); + } + + bool hasProcessing() { + return hasSysAdmin() || hasAdmin() || _has(privilege_processing); + } + + bool hasDeliveries() { + return hasSysAdmin() || hasAdmin() || _has(privilege_delivery); + } + + bool hasInvoices() { + return hasSysAdmin() || hasAdmin() || _has(privilege_invoice); + } + + bool hasShipment() { + return hasSysAdmin() || hasAdmin() || _has(privilege_shipment); + } + + bool hasCarton() { + return hasSysAdmin() || hasAdmin() || _has(privilege_carton); + } + + bool _has(String privilege) { + return (privileges != null ? privileges.contains(privilege) : false); } @override diff --git a/lib/helpers/firebase_helper.dart b/lib/helpers/firebase_helper.dart index bf72b7f..3a04030 100644 --- a/lib/helpers/firebase_helper.dart +++ b/lib/helpers/firebase_helper.dart @@ -30,3 +30,20 @@ Future uploadStorage(String path, File file, {String fileName}) async { print("meta:${await storageReference.getMetadata()}"); return downloadUrl; } + +Future deleteStorageFromUrls(List urls) async { + if (urls == null) return; + for (int i = 0; i < urls.length; i++) { + await deleteStorageFromUrl(urls[i]); + } +} + +Future deleteStorageFromUrl(String url) async { + try { + StorageReference storageReference = + await FirebaseStorage.instance.getReferenceFromUrl(url); + await storageReference.delete(); + } catch (e) { + log.warning("deleteStorage:$e"); + } +} diff --git a/lib/helpers/pagination.dart b/lib/helpers/pagination.dart new file mode 100644 index 0000000..56a60d8 --- /dev/null +++ b/lib/helpers/pagination.dart @@ -0,0 +1,70 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:logging/logging.dart'; + +/* + * Pagination load data in page + */ +class Pagination { + final log = Logger('PaginationModel'); + + final int rowPerLoad; + DocumentSnapshot prev; + bool ended = false; + + Query pageQuery; + + Pagination(Query pageQuery, {this.rowPerLoad = 20}) { + this.pageQuery = pageQuery; + initData(); + } + + void initData() async { + _clearState(); + load(); + } + + void _clearState() { + prev = null; + ended = false; + if (controller != null) controller.close(); + } + + StreamController controller; + Stream get stream { + if (controller != null) { + controller.close(); + } + controller = StreamController(onCancel: _clearState); + return controller.stream; + } + + void close() { + _clearState(); + } + + Future load() async { + Query _query = + prev != null ? pageQuery.startAfterDocument(prev) : pageQuery; + try { + await _query + .limit(rowPerLoad) + .getDocuments(source: Source.server) + .then((QuerySnapshot snapshot) { + int count = snapshot.documents.length; + ended = count < rowPerLoad; + prev = count > 0 ? snapshot.documents[count - 1] : prev; + snapshot.documents.forEach((e) { + controller.add(e); + }); + if (ended) { + controller.add(null); + } + }); + } catch (e) { + log.warning("Error!! $e"); + } + return ended; + } +} diff --git a/lib/helpers/pagination_model.dart b/lib/helpers/pagination_model.dart deleted file mode 100644 index ce0d192..0000000 --- a/lib/helpers/pagination_model.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'dart:async'; - -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:logging/logging.dart'; - -/* - * PaginationModel load data in page - * and listen to document change based on 'update_time' and 'delete_time' fields - * of the document - */ -class PaginationModel { - final log = Logger('PaginationModel'); - - List ids = []; - DocumentSnapshot prev; - int rowPerLoad = 10; - bool ended = false; - - StreamSubscription listener; - CollectionReference listeningCol; - Query pageQuery; - - PaginationModel(CollectionReference listeningCol, Query pageQuery, - {this.rowPerLoad = 10}) { - this.listeningCol = listeningCol; - this.pageQuery = pageQuery; - initData(); - } - - void initData() async { - _clearState(); - _initListener(); - load(); - } - - void _clearState() { - prev = null; - ids = []; - ended = false; - if (listener != null) listener.cancel(); - listener = null; - if (controller != null) controller.close(); - } - - StreamController controller; - Stream listen() { - if (controller != null) { - controller.close(); - } - controller = StreamController(onCancel: _clearState); - return controller.stream; - } - - void close() { - _clearState(); - } - - final String updateTimeField = 'update_time'; - final String deleteTimeField = 'delete_time'; - void _initListener() { - Query _query = - listeningCol.orderBy(updateTimeField, descending: true).limit(1); - _query.getDocuments(source: Source.server).then((QuerySnapshot snapshot) { - int count = snapshot.documents.length; - int updateTime = 0; - if (count == 1) { - updateTime = snapshot.documents[0].data[updateTimeField]; - } - - Query _queryListener = listeningCol - .where(updateTimeField, isGreaterThan: updateTime) - .orderBy(updateTimeField, descending: true); - - listener = - _queryListener.snapshots(includeMetadataChanges: true).listen((qs) { - qs.documentChanges.forEach((c) { - switch (c.type) { - case DocumentChangeType.added: - log.info("added!! $c"); - _update(c.document.documentID, c.document.data); - break; - case DocumentChangeType.modified: - log.info("modified!! $c"); - _update(c.document.documentID, c.document.data); - break; - default: - } - }); - }); - }); - } - - void _update(String id, Map data) { - if (ids.contains(id)) { - var deleted = data[deleteTimeField]; - if (deleted > 0) { - ids.remove(id); - controller.add(Result( - id: id, - data: data, - documentChangeType: DocumentChangeType.removed)); - } else { - controller.add(Result( - id: id, - data: data, - documentChangeType: DocumentChangeType.modified)); - } - } else { - ids.add(id); - controller.add(Result( - id: id, data: data, documentChangeType: DocumentChangeType.added)); - } - } - - Future load() async { - Query _query = - prev != null ? pageQuery.startAfterDocument(prev) : pageQuery; - try { - await _query - .where(deleteTimeField, isEqualTo: 0) - .limit(rowPerLoad) - .getDocuments(source: Source.server) - .then((QuerySnapshot snapshot) { - int count = snapshot.documents.length; - ended = count < rowPerLoad; - prev = count > 0 ? snapshot.documents[count - 1] : prev; - snapshot.documents.forEach((e) { - if (!ids.contains(e.documentID)) log.shout("load!! $e"); - ids.add(e.documentID); - controller.add(Result( - id: e.documentID, - data: e.data, - documentChangeType: DocumentChangeType.added)); - }); - if (ended) { - controller.add(Result(isEnded: true)); - } - }); - } catch (e) { - log.warning("Error!! $e"); - } - return ended; - } -} - -class Result { - String id; - Map data; - DocumentChangeType documentChangeType; - bool isEnded; - Result({this.id, this.data, this.documentChangeType, this.isEnded = false}); -} diff --git a/lib/helpers/shared_pref.dart b/lib/helpers/shared_pref.dart index c82fdb6..d2f26f8 100644 --- a/lib/helpers/shared_pref.dart +++ b/lib/helpers/shared_pref.dart @@ -27,6 +27,16 @@ class SharedPref { prefs.setString('language', lang); } + static Future getStaffMode() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getBool('staff_mode_on'); + } + + static Future saveStaffMode(bool staffMode) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setBool('staff_mode_on', staffMode); + } + static Future saveUser(User user) async { await _save("user", user.toJson()); } diff --git a/lib/pages/box/box_list.dart b/lib/pages/box/box_list.dart index 485e455..3447982 100644 --- a/lib/pages/box/box_list.dart +++ b/lib/pages/box/box_list.dart @@ -4,6 +4,7 @@ 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_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -38,7 +39,7 @@ class _BoxListState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(Icons.close), + icon: new Icon(CupertinoIcons.back), onPressed: () => Navigator.of(context).pop(), ), backgroundColor: primaryColor, diff --git a/lib/pages/chat/message_detail.dart b/lib/pages/chat/message_detail.dart index 3a85b61..14f884b 100644 --- a/lib/pages/chat/message_detail.dart +++ b/lib/pages/chat/message_detail.dart @@ -12,6 +12,7 @@ import 'package:fcs/pages/package/package_info.dart'; import 'package:fcs/pages/profile/profile_page.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -45,6 +46,10 @@ class MessageDetail extends StatelessWidget { return Scaffold( appBar: AppBar( + leading: new IconButton( + icon: new Icon(CupertinoIcons.back), + onPressed: () => Navigator.of(context).pop(), + ), backgroundColor: primaryColor, elevation: .9, title: Text( @@ -170,6 +175,7 @@ class MessageDetail extends StatelessWidget { PackageModel packageModel = Provider.of(context, listen: false); Package p = await packageModel.getPackage(message.messageID); + if (p == null) return; Navigator.push(context, BottomUpPageRoute(PackageInfo(package: p))); } if (message.messageType == message_type_profile && diff --git a/lib/pages/delivery/delivery_list.dart b/lib/pages/delivery/delivery_list.dart index 1e958b6..2314714 100644 --- a/lib/pages/delivery/delivery_list.dart +++ b/lib/pages/delivery/delivery_list.dart @@ -2,6 +2,7 @@ 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/progress.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -35,7 +36,7 @@ class _DeliverListState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(Icons.close), + icon: new Icon(CupertinoIcons.back), onPressed: () => Navigator.of(context).pop(), ), backgroundColor: primaryColor, diff --git a/lib/pages/delivery_address/model/delivery_address_model.dart b/lib/pages/delivery_address/model/delivery_address_model.dart index 73df666..2609061 100644 --- a/lib/pages/delivery_address/model/delivery_address_model.dart +++ b/lib/pages/delivery_address/model/delivery_address_model.dart @@ -21,6 +21,12 @@ class DeliveryAddressModel extends BaseModel { _loadDeliveryAddresses(); } + @override + logout() async { + if (listener != null) await listener.cancel(); + deliveryAddresses = []; + } + Future _loadDeliveryAddresses() async { if (user == null) return; String path = "$delivery_address_collection/"; diff --git a/lib/pages/discount/discount_list.dart b/lib/pages/discount/discount_list.dart index 0a1a9f6..e9e636a 100644 --- a/lib/pages/discount/discount_list.dart +++ b/lib/pages/discount/discount_list.dart @@ -4,6 +4,7 @@ import 'package:fcs/pages/discount/model/discount_model.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -29,7 +30,7 @@ class _DiscountListState extends State { AppTranslations.of(context).text("discount.title"), ), leading: new IconButton( - icon: new Icon(Icons.close), + icon: new Icon(CupertinoIcons.back), onPressed: () => Navigator.of(context).pop(), ), backgroundColor: primaryColor, diff --git a/lib/pages/fcs_shipment/model/fcs_shipment_model.dart b/lib/pages/fcs_shipment/model/fcs_shipment_model.dart index 2ec6051..d0da185 100644 --- a/lib/pages/fcs_shipment/model/fcs_shipment_model.dart +++ b/lib/pages/fcs_shipment/model/fcs_shipment_model.dart @@ -20,7 +20,7 @@ class FcsShipmentModel extends BaseModel { } Future _loadFcsShipments() async { - if (user == null) return; + if (user == null || !user.hasFcsShipments()) return; String path = "/$fcs_shipment_collection/"; if (listener != null) listener.cancel(); fcsShipments = []; @@ -49,6 +49,7 @@ class FcsShipmentModel extends BaseModel { @override logout() async { + if (listener != null) await listener.cancel(); fcsShipments = []; } diff --git a/lib/pages/invoice/invoce_list.dart b/lib/pages/invoice/invoce_list.dart index 0133dfc..4ae1884 100644 --- a/lib/pages/invoice/invoce_list.dart +++ b/lib/pages/invoice/invoce_list.dart @@ -8,6 +8,7 @@ import 'package:fcs/pages/user_search/user_serach.dart'; import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; @@ -45,7 +46,7 @@ class _InvoiceListState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(Icons.close), + icon: new Icon(CupertinoIcons.back), onPressed: () => Navigator.of(context).pop(), ), backgroundColor: primaryColor, diff --git a/lib/pages/main/home_page.dart b/lib/pages/main/home_page.dart index c7d4b8a..d0d18fa 100644 --- a/lib/pages/main/home_page.dart +++ b/lib/pages/main/home_page.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'package:fcs/data/services/services.dart'; +import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/user.dart'; +import 'package:fcs/helpers/shared_pref.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/localization/transalation.dart'; import 'package:fcs/pages/box/box_list.dart'; @@ -17,23 +19,27 @@ import 'package:fcs/pages/fcs_shipment/fcs_shipment_list.dart'; import 'package:fcs/pages/invoice/invoce_list.dart'; import 'package:fcs/pages/main/model/language_model.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/package/package_info.dart'; import 'package:fcs/pages/package/package_list.dart'; import 'package:fcs/pages/processing/processing_list.dart'; import 'package:fcs/pages/rates/shipment_rates.dart'; import 'package:fcs/pages/receiving/receiving_list.dart'; import 'package:fcs/pages/shipment/shipment_list.dart'; import 'package:fcs/pages/staff/staff_list.dart'; -import 'package:fcs/pages/widgets/task_button.dart'; import 'package:fcs/pages/widgets/badge.dart'; import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/bottom_widgets.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pages/widgets/right_left_page_rout.dart'; +import 'package:fcs/pages/widgets/task_button.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; @@ -52,10 +58,12 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { final log = Logger('_HomePageState'); bool login = false; - bool customer = true; + bool _isLoading = false; List isSelected = [true, false]; static FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + TextEditingController _searchCtl = TextEditingController(); + List isFcs = [false]; @override void initState() { @@ -73,6 +81,18 @@ class _HomePageState extends State { mainModel.setMessaginToken = token; }); _initLocalNotifications(); + _loadStaffMode(mainModel.isCustomer()); + } + + _loadStaffMode(bool isCustomer) async { + bool staffMode = await SharedPref.getStaffMode(); + setState(() { + if (isCustomer) { + isFcs[0] = false; + } else { + isFcs[0] = staffMode; + } + }); } String notiUserID, notiUserName; @@ -189,7 +209,6 @@ class _HomePageState extends State { if (user == null) { return Container(); } - customer = Provider.of(context).isCustomer(); login = Provider.of(context).isLogin(); LanguageModel languageModel = Provider.of(context); @@ -204,6 +223,12 @@ class _HomePageState extends State { icon: Octicons.package, btnCallback: () => Navigator.of(context).push( CupertinoPageRoute(builder: (context) => PackageList()))); + final packagesBtnFcs = TaskButton("package.btn.name", + icon: Octicons.package, + btnCallback: () => Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => PackageList( + onlyFcs: true, + )))); final receivingBtn = TaskButton("receiving.title", icon: MaterialCommunityIcons.inbox_arrow_down, @@ -214,15 +239,20 @@ class _HomePageState extends State { icon: FontAwesome.dropbox, btnCallback: () => Navigator.of(context).push( CupertinoPageRoute(builder: (context) => ProcessingList()))); - final boxesBtn = TaskButton("boxes.name", + final cartonBtn = TaskButton("boxes.name", icon: MaterialCommunityIcons.package, - btnCallback: () => - Navigator.of(context).push(BottomUpPageRoute(BoxList()))); + btnCallback: () => Navigator.of(context) + .push(CupertinoPageRoute(builder: (context) => BoxList()))); - final pickUpBtn = TaskButton("shipment", + final shipmentBtn = TaskButton("shipment", icon: SimpleLineIcons.direction, - btnCallback: () => - Navigator.of(context).push(BottomUpPageRoute(ShipmentList()))); + btnCallback: () => Navigator.of(context) + .push(CupertinoPageRoute(builder: (context) => ShipmentList()))); + + final shipmentBtnFcs = TaskButton("shipment", + icon: SimpleLineIcons.direction, + btnCallback: () => Navigator.of(context) + .push(CupertinoPageRoute(builder: (context) => ShipmentList()))); final shipmentCostBtn = TaskButton("rate", icon: FontAwesomeIcons.calculator, @@ -231,21 +261,19 @@ class _HomePageState extends State { final fcsShipmentBtn = TaskButton("FCSshipment.title", icon: Ionicons.ios_airplane, - btnCallback: () => Navigator.of(context).push(CupertinoPageRoute( + btnCallback: () => Navigator.of(context).push(CupertinoPageRoute( builder: (context) => FcsShipmentList(), ))); - final notiBtnOrg = TaskButton("message.btn", icon: Icons.message, btnCallback: () { MessageModel messageModel = Provider.of(context, listen: false); messageModel.initQuery(user.id); - Navigator.push( - context, - BottomUpPageRoute(MessageDetail( - messageModel: messageModel, - )), - ).then((value) { + Navigator.of(context) + .push(CupertinoPageRoute( + builder: (context) => MessageDetail(messageModel: messageModel), + )) + .then((value) { if (user.userUnseenCount > 0) { messageModel.seenMessages(user.id, true); } @@ -272,128 +300,204 @@ class _HomePageState extends State { final invoicesBtn = TaskButton("invoices.btn", icon: FontAwesomeIcons.fileInvoice, - btnCallback: () => - Navigator.of(context).push(BottomUpPageRoute(InvoiceList()))); + btnCallback: () => Navigator.of(context).push( + CupertinoPageRoute(builder: (context) => InvoiceList()))); + final invoicesBtnFcs = TaskButton("invoices.btn", + icon: FontAwesomeIcons.fileInvoice, + btnCallback: () => Navigator.of(context).push( + CupertinoPageRoute(builder: (context) => InvoiceList()))); final discountBtn = TaskButton("discount.btn", icon: Entypo.price_ribbon, - btnCallback: () => - Navigator.of(context).push(BottomUpPageRoute(DiscountList()))); + btnCallback: () => Navigator.of(context).push( + CupertinoPageRoute(builder: (context) => DiscountList()))); final deliveryBtn = TaskButton("delivery.title", icon: MaterialCommunityIcons.truck_fast, - btnCallback: () => - Navigator.of(context).push(BottomUpPageRoute(DeliverList()))); + btnCallback: () => Navigator.of(context).push( + CupertinoPageRoute(builder: (context) => DeliverList()))); List widgets = []; - if (user != null) { - true ? widgets.add(pickUpBtn) : ""; - !customer ? widgets.add(fcsShipmentBtn) : ""; - customer ? widgets.add(notiBtn) : ""; - user.hasStaffs() ? widgets.add(staffBtn) : ""; - widgets.add(shipmentCostBtn); - user.hasPackages() ? widgets.add(packagesBtn) : ""; - user.hasPackages() ? widgets.add(receivingBtn) : ""; - user.hasPackages() ? widgets.add(processingBtn) : ""; - true ? widgets.add(boxesBtn) : ""; - true ? widgets.add(deliveryBtn) : ""; - user.hasCustomers() ? widgets.add(customersBtn) : ""; - true ? widgets.add(invoicesBtn) : ""; - // true ? widgets.add(discountBtn) : ""; - } + widgets.add(notiBtn); + widgets.add(packagesBtn); + widgets.add(shipmentBtn); + widgets.add(invoicesBtn); widgets.add(faqBtn); - return OfflineRedirect( - child: FlavorBanner( - child: Scaffold( - appBar: AppBar( - elevation: 0, - backgroundColor: primaryColor, - title: ClipRRect( - child: Image.asset("assets/logo.jpg", height: 40), - borderRadius: new BorderRadius.circular(30.0), - ), - actions: login - ? [ - ToggleButtons( - children: [ - Image.asset( - 'icons/flags/png/us.png', - package: 'country_icons', - fit: BoxFit.fitWidth, - width: 25, - ), - Image.asset( - 'icons/flags/png/mm.png', - package: 'country_icons', - fit: BoxFit.fitWidth, - width: 25, - ) - ], - onPressed: _langChange, - isSelected: languageModel.currentState, - selectedBorderColor: Colors.white24, - ), - IconButton( - onPressed: () { - Navigator.of(context) - .push(RightLeftPageRoute(Profile())); - }, - iconSize: 30, - icon: Icon(Icons.account_circle), - ), - ] - : [ - ToggleButtons( - children: [ - Image.asset( - 'icons/flags/png/us.png', - package: 'country_icons', - fit: BoxFit.fitWidth, - width: 25, - ), - Image.asset( - 'icons/flags/png/mm.png', - package: 'country_icons', - fit: BoxFit.fitWidth, - width: 25, - ) - ], - onPressed: _langChange, - isSelected: languageModel.currentState, - ), - FlatButton( - onPressed: () { - Navigator.of(context) - .push(BottomUpPageRoute(SigninPage())); - }, - child: Text( - "Sign In", - style: siginButtonStyle, - ), + widgets.add(shipmentCostBtn); + + List widgetsFcs = []; + if (user.hasPackages()) widgetsFcs.add(packagesBtnFcs); + if (user.hasShipment()) widgetsFcs.add(shipmentBtnFcs); + if (user.hasInvoices()) widgetsFcs.add(invoicesBtnFcs); + + if (user.hasFcsShipments()) widgetsFcs.add(fcsShipmentBtn); + if (user.hasReceiving()) widgetsFcs.add(receivingBtn); + if (user.hasProcessing()) widgetsFcs.add(processingBtn); + if (user.hasCarton()) widgetsFcs.add(cartonBtn); + if (user.hasDeliveries()) widgetsFcs.add(deliveryBtn); + if (user.hasCustomers()) widgetsFcs.add(customersBtn); + if (user.hasAdmin()) widgetsFcs.add(discountBtn); + if (user.hasStaffs()) widgetsFcs.add(staffBtn); + + final fcsToggle = ToggleButtons( + selectedColor: Colors.white, + children: [ + Icon(MaterialCommunityIcons.worker), + ], + onPressed: (i) => this.setState(() { + isFcs[0] = !isFcs[0]; + SharedPref.saveStaffMode(isFcs[0]); + }), + isSelected: isFcs, + selectedBorderColor: Colors.white24, + ); + final langToggle = ToggleButtons( + children: [ + Image.asset( + 'icons/flags/png/us.png', + package: 'country_icons', + fit: BoxFit.fitWidth, + width: 25, + ), + Image.asset( + 'icons/flags/png/mm.png', + package: 'country_icons', + fit: BoxFit.fitWidth, + width: 25, + ) + ], + onPressed: _langChange, + isSelected: languageModel.currentState, + selectedBorderColor: Colors.white24, + ); + final signinBtn = FlatButton( + onPressed: () { + Navigator.of(context).push(BottomUpPageRoute(SigninPage())); + }, + child: Text( + "Sign In", + style: siginButtonStyle, + ), + ); + final profileBtn = IconButton( + onPressed: () { + Navigator.of(context).push(RightLeftPageRoute(Profile())); + }, + iconSize: 30, + icon: Icon(Icons.account_circle), + ); + + var searchInput = Row(children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 3, left: 5, right: 5, top: 3), + child: Theme( + data: new ThemeData( + primaryColor: primaryColor, + primaryColorDark: primaryColor, + ), + child: TextField( + style: TextStyle(color: Colors.white), + controller: _searchCtl, + scrollPadding: EdgeInsets.all(0), + decoration: new InputDecoration( + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + borderSide: + const BorderSide(color: Colors.white, width: 1.5), + ), + focusedBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + borderSide: + const BorderSide(color: Colors.white, width: 1.5), + ), + contentPadding: EdgeInsets.only(top: 1, bottom: 1), + isDense: true, + hintText: getLocalString(context, "home.search"), + hintStyle: languageModel.isEng + ? newLabelStyle(color: Colors.white60) + : newLabelStyleMM(color: Colors.white60), + prefixIcon: const Icon( + Icons.search, + color: Colors.grey, + ), + suffixIcon: InkWell( + onTap: () => {_searchCtl.clear()}, + child: const Icon( + Icons.close, + color: Colors.grey, + ), + ), + suffixStyle: const TextStyle(color: primaryColor)), + ), + ), + ), + ), + InkWell( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: LocalText( + context, + "home.search.btn", + color: Colors.white, + ), + ), + onTap: _lookup, + ) + ]); + widgets.insert(0, searchInput); + + return LocalProgress( + inAsyncCall: _isLoading, + child: OfflineRedirect( + child: FlavorBanner( + child: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: primaryColor, + title: ClipRRect( + child: Image.asset("assets/logo.jpg", height: 40), + borderRadius: new BorderRadius.circular(30.0), + ), + actions: login + ? user.isCustomer() + ? [ + langToggle, + profileBtn, + ] + : [ + fcsToggle, + langToggle, + profileBtn, + ] + : [ + langToggle, + signinBtn, + ]), + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xd0272262), + Color(0xfa272262), + ], + ), + ), + child: ListView( + children: [ + Column(children: [ + Wrap( + alignment: WrapAlignment.center, + children: isFcs[0] ? widgetsFcs : widgets, ), ]), - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color(0xd0272262), - Color(0xfa272262), + SizedBox(height: 50), + BottomWidgets(), ], - ), - ), - child: ListView( - children: [ - Column(children: [ - Wrap( - alignment: WrapAlignment.center, - children: widgets, - ), - ]), - BottomWidgets(), - ], - ))), + ))), + ), ), ); } @@ -408,4 +512,34 @@ class _HomePageState extends State { isSelected[index] = !isSelected[index]; }); } + + _lookup() async { + setState(() { + _isLoading = true; + }); + + try { + String term = _searchCtl.text; + if (term == null || term.trim() == "") return; + var packageModel = Provider.of(context, listen: false); + Package package = await packageModel.lookupPackage(term); + if (package == null) { + showMsgDialog(context, "Not found", "Tracking ID - '$term' not found!"); + return; + } + Navigator.push( + context, + BottomUpPageRoute(PackageInfo( + package: package, + isSearchResult: true, + )), + ); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } } diff --git a/lib/pages/package/model/package_model.dart b/lib/pages/package/model/package_model.dart index 4633a5a..5411e5c 100644 --- a/lib/pages/package/model/package_model.dart +++ b/lib/pages/package/model/package_model.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:io'; import 'package:fcs/data/services/services.dart'; +import 'package:fcs/domain/vo/message.dart'; +import 'package:fcs/helpers/pagination.dart'; import 'package:path/path.dart' as Path; import 'package:cloud_firestore/cloud_firestore.dart'; @@ -15,37 +17,97 @@ class PackageModel extends BaseModel { final log = Logger('PackageModel'); StreamSubscription listener; + StreamSubscription customerPackageListener; + Pagination pagination; + List packages = []; + List customerPackages = []; + List deliveredPackages = []; + + bool endOfDeliveredPackages = false; + bool isLoading = false; @override void privilegeChanged() { super.privilegeChanged(); _loadPackages(); + _loadCustomerPackages(); } @override logout() async { if (listener != null) await listener.cancel(); + if (customerPackageListener != null) await customerPackageListener.cancel(); + if (pagination != null) pagination.close(); packages = []; + customerPackages = []; + deliveredPackages = []; + } + + Future initDeliveredPackages() { + if (pagination != null) pagination.close(); + deliveredPackages = []; + endOfDeliveredPackages = false; + isLoading = false; + + var pageQuery = Firestore.instance + .collection("/$packages_collection") + // .collection( + // "/users/8OTfsbVvsUOn1SLxy1OrKk7Y_yNKkVoGalPcIlcHnAY/messages") + // .orderBy("date", descending: true); + .where("is_delivered", isEqualTo: true) + .where("is_deleted", isEqualTo: false); + if (user.isCustomer()) { + pageQuery = pageQuery.where("user_id", isEqualTo: user.id); + } + pageQuery = pageQuery.orderBy("current_status_date", descending: true); + pagination = new Pagination(pageQuery, rowPerLoad: 20); + pagination.stream.listen((doc) { + if (doc == null) { + endOfDeliveredPackages = true; + } else { + deliveredPackages.add(Package.fromMap(doc.data, doc.documentID)); + // var m = Message.fromMap(doc.data, doc.documentID); + // deliveredPackages.add(Package( + // id: m.id, + // status: package_delivered_status, + // currentStatus: package_delivered_status, + // currentStatusDate: m.date, + // trackingID: (count++).toString(), + // market: m.message)); + } + }); + return null; + } + + Future loadMoreDeliveredPackages() { + if (pagination != null && !isLoading && !endOfDeliveredPackages) { + isLoading = true; + notifyListeners(); + pagination.load().then((value) { + isLoading = false; + notifyListeners(); + }); + } + return null; } Future _loadPackages() async { - if (user == null) return; - String path = ""; - if (user.isCustomer()) { - path = "/$user_collection/${user.id}/$packages_collection"; - } else { - path = "/$packages_collection"; - } + if (user == null || + !user.hasPackages() || + !user.hasReceiving() || + !user.hasProcessing()) return; + String path = "/$packages_collection"; if (listener != null) listener.cancel(); packages = []; try { - listener = Firestore.instance + var q = Firestore.instance .collection("$path") .where("is_delivered", isEqualTo: false) - .snapshots() - .listen((QuerySnapshot snapshot) { + .where("is_deleted", isEqualTo: false); + + listener = q.snapshots().listen((QuerySnapshot snapshot) { packages.clear(); packages = snapshot.documents.map((documentSnapshot) { var package = Package.fromMap( @@ -59,14 +121,36 @@ class PackageModel extends BaseModel { } } + Future _loadCustomerPackages() async { + if (user == null) return; + String path = "/$packages_collection"; + if (customerPackageListener != null) customerPackageListener.cancel(); + customerPackages = []; + + try { + var q = Firestore.instance + .collection("$path") + .where("is_delivered", isEqualTo: false) + .where("is_deleted", isEqualTo: false) + .where("user_id", isEqualTo: user.id); + + customerPackageListener = q.snapshots().listen((QuerySnapshot snapshot) { + customerPackages.clear(); + customerPackages = snapshot.documents.map((documentSnapshot) { + var package = Package.fromMap( + documentSnapshot.data, documentSnapshot.documentID); + return package; + }).toList(); + notifyListeners(); + }); + } catch (e) { + log.warning("Error!! $e"); + } + } + Future getPackage(String id) async { if (user == null) return null; - String path = ""; - if (user.isCustomer()) { - path = "/$user_collection/${user.id}/$packages_collection"; - } else { - path = "/$packages_collection"; - } + String path = "/$packages_collection"; try { DocumentSnapshot snap = await Firestore.instance.collection("$path").document(id).get(); @@ -80,6 +164,42 @@ class PackageModel extends BaseModel { return null; } + Future lookupPackage(String trackingID) async { + if (user == null) return null; + String path = "/$packages_collection"; + + try { + var qsnap = await Firestore.instance + .collection("$path") + .where("tracking_id", isEqualTo: trackingID) + .where("has_user_id", isEqualTo: false) + .getDocuments(source: Source.server); + if (qsnap.documents.length > 0) { + var snap = qsnap.documents[0]; + if (snap.exists) { + var package = Package.fromMap(snap.data, snap.documentID); + return package; + } + } + + qsnap = await Firestore.instance + .collection("$path") + .where("tracking_id", isEqualTo: trackingID) + .where("user_id", isEqualTo: user.id) + .getDocuments(source: Source.server); + if (qsnap.documents.length > 0) { + var snap = qsnap.documents[0]; + if (snap.exists) { + var package = Package.fromMap(snap.data, snap.documentID); + return package; + } + } + } catch (e) { + log.warning("Error!! $e"); + } + return null; + } + Future> searchUser(String term) { return Services.instance.userService.searchUser(term); } @@ -93,8 +213,8 @@ class PackageModel extends BaseModel { .createPackages(packages, user.fcsID); } - Future createPackage(User user, Package package, List files, - List deletedUrls) async { + Future createReceiving( + User user, Package package, List files) async { if (user != null) { package.fcsID = user.fcsID; } @@ -106,28 +226,61 @@ class PackageModel extends BaseModel { String url = await uploadStorage(path, f); package.photoUrls.add(url); } - package.photoUrls.removeWhere((e) => deletedUrls.contains(e)); } - return Services.instance.packageService.createPackage(package); + return Services.instance.packageService.createReceiving(package); } - Future completeProcessing( - Package package, List files, List deletedUrls) async { + Future updateReceiving(User user, Package package, List files, + List deletedUrls) async { + if (user != null) { + package.fcsID = user.fcsID; + } + if (deletedUrls != null) { + for (String url in deletedUrls) { + package.photoUrls.remove(url); + } + await deleteStorageFromUrls(deletedUrls); + } + if (files != null) { if (files.length > 5) throw Exception("Exceed number of file upload"); package.photoUrls = package.photoUrls == null ? [] : package.photoUrls; for (File f in files) { - String path = Path.join(pkg_files_path, package.userID, package.id); + String path = Path.join(pkg_files_path); + String url = await uploadStorage(path, f); + package.photoUrls.add(url); + } + } + await Services.instance.packageService.updateReceiving(package); + } + + Future deleteReceiving(Package package) { + return Services.instance.packageService.deleteReceiving(package); + } + + Future updateProcessing( + Package package, List files, List deletedUrls) async { + if (deletedUrls != null) { + for (String url in deletedUrls) { + package.photoUrls.remove(url); + } + await deleteStorageFromUrls(deletedUrls); + } + + if (files != null) { + if (files.length > 5) throw Exception("Exceed number of file upload"); + package.photoUrls = package.photoUrls == null ? [] : package.photoUrls; + for (File f in files) { + String path = Path.join(pkg_files_path); String url = await uploadStorage(path, f); package.photoUrls.add(url); } package.photoUrls.removeWhere((e) => deletedUrls.contains(e)); } - await request("/package", "PUT", - payload: package.toJson(), token: await getToken()); + await Services.instance.packageService.updateProcessing(package); } - Future deletePackage(Package package) { - return Services.instance.packageService.deletePackage(package); + Future deleteProcessing(Package package) { + return Services.instance.packageService.deleteProcessing(package); } } diff --git a/lib/pages/package/package_editor.dart b/lib/pages/package/package_editor.dart index b6b48db..06bbf07 100644 --- a/lib/pages/package/package_editor.dart +++ b/lib/pages/package/package_editor.dart @@ -228,8 +228,8 @@ class _PackageEditorPageState extends State { _package.desc = _descCtl.text; _package.remark = _remarkCtl.text; _package.market = selectedMarket; - await packageModel.completeProcessing(_package, - multiImgController.getAddedFile, multiImgController.getDeletedUrl); + // await packageModel.completeProcessing(_package, + // multiImgController.getAddedFile, multiImgController.getDeletedUrl); Navigator.pop(context); } catch (e) { showMsgDialog(context, "Error", e.toString()); @@ -251,7 +251,7 @@ class _PackageEditorPageState extends State { PackageModel packageModel = Provider.of(context, listen: false); try { - await packageModel.deletePackage(_package); + // await packageModel.deletePackage(_package); Navigator.pop(context, true); } catch (e) { showMsgDialog(context, "Error", e.toString()); diff --git a/lib/pages/package/package_info.dart b/lib/pages/package/package_info.dart index 0e635ff..9134a8a 100644 --- a/lib/pages/package/package_info.dart +++ b/lib/pages/package/package_info.dart @@ -1,28 +1,24 @@ import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/model/main_model.dart'; -import 'package:fcs/pages/package/package_editor.dart'; -import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/display_text.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/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'; -import 'model/package_model.dart'; - final DateFormat dateFormat = DateFormat("d MMM yyyy"); class PackageInfo extends StatefulWidget { + final isSearchResult; final Package package; - PackageInfo({this.package}); + PackageInfo({this.package, this.isSearchResult = false}); @override _PackageInfoState createState() => _PackageInfoState(); @@ -54,8 +50,6 @@ class _PackageInfoState extends State { @override Widget build(BuildContext context) { - bool isCustomer = Provider.of(context).isCustomer(); - final trackingIdBox = DisplayText( text: _package.trackingID, labelTextKey: "package.tracking.id", @@ -109,14 +103,6 @@ class _PackageInfoState extends State { fontSize: 20, color: primaryColor, ), - actions: [ - isCustomer - ? Container() - : IconButton( - icon: Icon(Icons.edit, color: primaryColor), - onPressed: _gotoEditor, - ) - ], ), body: Card( child: Column( @@ -126,29 +112,15 @@ class _PackageInfoState extends State { padding: const EdgeInsets.all(10.0), child: ListView(children: [ trackingIdBox, - customerNameBox, - marketBox, + widget.isSearchResult ? Container() : customerNameBox, + widget.isSearchResult ? Container() : marketBox, statusBox, _package.photoUrls.length == 0 ? Container() : img, - descBox, + widget.isSearchResult ? Container() : descBox, remarkBox, - ExpansionTile( - initiallyExpanded: true, - title: Text( - 'Status', - style: TextStyle( - color: primaryColor, fontWeight: FontWeight.bold), - ), - children: [ - Container( - padding: EdgeInsets.only(left: 20), - height: 400, - child: Timeline( - children: _models(), - position: TimelinePosition.Left), - ), - ], - ), + StatusTree( + shipmentHistory: _package.shipmentHistory, + currentStatus: _package.currentStatus), SizedBox( height: 20, ) @@ -194,20 +166,4 @@ class _PackageInfoState extends State { ))) .toList(); } - - _gotoEditor() async { - bool deleted = await Navigator.push( - context, - BottomUpPageRoute(PackageEditorPage( - package: widget.package, - ))); - if (deleted ?? false) { - Navigator.pop(context); - } else { - PackageModel packageModel = - Provider.of(context, listen: false); - Package p = await packageModel.getPackage(_package.id); - initPackage(p); - } - } } diff --git a/lib/pages/package/package_list.dart b/lib/pages/package/package_list.dart index b0613e9..8bad4bd 100644 --- a/lib/pages/package/package_list.dart +++ b/lib/pages/package/package_list.dart @@ -1,14 +1,12 @@ import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/localization/app_translations.dart'; -import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/package/package_info.dart'; import 'package:fcs/pages/package/package_list_row.dart'; -import 'package:fcs/pages/package/package_new.dart'; import 'package:fcs/pages/package_search/package_serach.dart'; -import 'package:fcs/pages/user_search/user_serach.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'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; @@ -16,16 +14,29 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class PackageList extends StatefulWidget { + final bool onlyFcs; + + const PackageList({Key key, this.onlyFcs = false}) : super(key: key); @override _PackageListState createState() => _PackageListState(); } class _PackageListState extends State { bool _isLoading = false; + bool _showDelivered = false; + var _controller = ScrollController(); @override void initState() { super.initState(); + Provider.of(context, listen: false).initDeliveredPackages(); + _controller.addListener(() { + if (_showDelivered && + _controller.position.pixels == _controller.position.maxScrollExtent) { + Provider.of(context, listen: false) + .loadMoreDeliveredPackages(); + } + }); } @override @@ -36,7 +47,20 @@ class _PackageListState extends State { @override Widget build(BuildContext context) { var packageModel = Provider.of(context); - bool isCustomer = context.select((MainModel m) => m.isCustomer()); + bool onlyFcs = widget.onlyFcs; + var packages = _showDelivered + ? packageModel.deliveredPackages + : onlyFcs ? packageModel.packages : packageModel.customerPackages; + final popupMenu = LocalPopupMenuButton( + popmenus: [ + LocalPopupMenu( + id: 1, textKey: "package.popupmenu.active", selected: true), + LocalPopupMenu(id: 2, textKey: "package.popupmenu.delivered") + ], + popupMenuCallback: (p) => this.setState(() { + _showDelivered = p.id == 2; + }), + ); return LocalProgress( inAsyncCall: _isLoading, @@ -55,9 +79,8 @@ class _PackageListState extends State { color: Colors.white, ), actions: [ - isCustomer - ? Container() - : IconButton( + onlyFcs + ? IconButton( icon: Icon( Icons.search, color: Colors.white, @@ -65,41 +88,45 @@ class _PackageListState extends State { iconSize: 30, onPressed: () => searchPackage(context, callbackPackageSelect: _searchCallback), - ), + ) + : Container(), + popupMenu ], ), - floatingActionButton: isCustomer - ? Container() - : FloatingActionButton.extended( - onPressed: () { - _newPackage(); - }, - icon: Icon(Icons.add), - label: Text( - AppTranslations.of(context).text("package.create.title")), - backgroundColor: primaryColor, - ), - body: new ListView.separated( - separatorBuilder: (context, index) => Divider( - color: Colors.black, - ), - scrollDirection: Axis.vertical, - padding: EdgeInsets.only(top: 15), - shrinkWrap: true, - itemCount: packageModel.packages.length, - itemBuilder: (BuildContext context, int index) { - return PackageListRow( - key: ValueKey(packageModel.packages[index].id), - package: packageModel.packages[index], - ); - })), - ); - } - - _newPackage() { - Navigator.push( - context, - BottomUpPageRoute(PackageNew()), + body: Column( + children: [ + Expanded( + child: ListView.separated( + controller: _controller, + separatorBuilder: (context, index) => Divider( + color: Colors.black, + ), + scrollDirection: Axis.vertical, + padding: EdgeInsets.only(top: 15), + shrinkWrap: true, + itemCount: packages.length, + itemBuilder: (BuildContext context, int index) { + return PackageListRow( + key: ValueKey(packages[index].id), + package: packages[index], + ); + }), + ), + packageModel.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/processing/processing_editor.dart b/lib/pages/processing/processing_editor.dart index 8cab244..70f38a4 100644 --- a/lib/pages/processing/processing_editor.dart +++ b/lib/pages/processing/processing_editor.dart @@ -1,13 +1,16 @@ import 'package:fcs/domain/entities/market.dart'; import 'package:fcs/domain/entities/package.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/widgets/bottom_up_page_route.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'; @@ -31,6 +34,7 @@ class _ProcessingEditorState extends State { TextEditingController _descCtl = new TextEditingController(); Package _package; + User _user; bool _isLoading = false; @override @@ -41,6 +45,10 @@ class _ProcessingEditorState extends State { _descCtl.text = _package.desc; _remarkCtl.text = _package.remark; multiImgController.setImageUrls = _package.photoUrls; + _user = User( + fcsID: _package.fcsID ?? "", + name: _package.userName ?? "", + phoneNumber: _package.phoneNumber ?? ""); } final DateFormat dateFormat = DateFormat("d MMM yyyy"); @@ -50,21 +58,39 @@ class _ProcessingEditorState extends State { @override Widget build(BuildContext context) { + var fcsIDBox = Row( + children: [ + Expanded( + child: DisplayText( + text: _user.fcsID, + labelTextKey: "processing.fcs.id", + icon: FcsIDIcon(), + )), + IconButton( + icon: Icon(Icons.search, color: primaryColor), + onPressed: () => searchUser(context, callbackUserSelect: (u) { + setState(() { + this._user = u; + }); + })), + ], + ); + final namebox = DisplayText( + text: _user.name, + labelTextKey: "processing.name", + iconData: Icons.person, + ); + final phoneNumberBox = DisplayText( + text: _user.phoneNumber, + labelTextKey: "processing.phone", + iconData: Icons.phone, + ); + final trackingIdBox = DisplayText( text: _package.trackingID, labelTextKey: "processing.tracking.id", iconData: MaterialCommunityIcons.barcode_scan, ); - final statusBox = DisplayText( - text: _package.currentStatus, - labelTextKey: "processing.status", - iconData: AntDesign.exclamationcircleo, - ); - final customerNameBox = DisplayText( - text: _package.userName, - labelTextKey: "processing.name", - iconData: Icons.perm_identity, - ); final completeProcessingBtn = fcsButton( context, getLocalString(context, 'processing.edit.complete.btn'), @@ -101,44 +127,19 @@ class _ProcessingEditorState extends State { fontSize: 20, color: primaryColor, ), - actions: [ - IconButton( - icon: Icon(Icons.delete, color: primaryColor), - onPressed: _delete, - ) - ], ), body: Padding( padding: const EdgeInsets.all(8.0), child: ListView( children: [ trackingIdBox, - customerNameBox, - statusBox, - Divider(), - Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: LocalText( - context, - "processing.edit.sub_title", - color: primaryColor, - fontSize: 16, - fontWeight: FontWeight.w700, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 18.0, right: 10), - child: Column( - children: [ - marketDropdown(), - descBox, - remarkBox, - img, - ], - ), - ), + fcsIDBox, + namebox, + phoneNumberBox, + marketDropdown(), + descBox, + remarkBox, + img, completeProcessingBtn, SizedBox( height: 20, @@ -159,51 +160,54 @@ class _ProcessingEditorState extends State { markets.insert(0, selectedMarket); } - return Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 18.0), - child: LocalText( - context, - "processing.market", - color: primaryColor, - fontSize: 16, - ), - ), - Container( - width: 150, - child: DropdownButton( - value: selectedMarket, - style: TextStyle(color: Colors.black, fontSize: 14), - underline: Container( - height: 1, - color: Colors.grey, + return Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 18.0), + child: LocalText( + context, + "processing.market", + color: primaryColor, + fontSize: 16, ), - onChanged: (String newValue) { - setState(() { - if (newValue == MANAGE_MARKET) { - selectedMarket = null; - _manageMarket(); - return; - } - selectedMarket = newValue; - }); - }, - isExpanded: true, - items: markets.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value ?? "", - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: value == MANAGE_MARKET - ? secondaryColor - : primaryColor)), - ); - }).toList(), ), - ), - ], + Container( + width: 150, + child: DropdownButton( + value: selectedMarket, + style: TextStyle(color: Colors.black, fontSize: 14), + underline: Container( + height: 1, + color: Colors.grey, + ), + onChanged: (String newValue) { + setState(() { + if (newValue == MANAGE_MARKET) { + selectedMarket = null; + _manageMarket(); + return; + } + selectedMarket = newValue; + }); + }, + isExpanded: true, + items: markets.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value ?? "", + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: value == MANAGE_MARKET + ? secondaryColor + : primaryColor)), + ); + }).toList(), + ), + ), + ], + ), ); } @@ -219,16 +223,21 @@ class _ProcessingEditorState extends State { showMsgDialog(context, "Error", "Expected some description"); return; } + 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.completeProcessing(_package, + await packageModel.updateProcessing(_package, multiImgController.getAddedFile, multiImgController.getDeletedUrl); Navigator.pop(context); } catch (e) { @@ -239,26 +248,4 @@ class _ProcessingEditorState extends State { }); } } - - _delete() { - showConfirmDialog(context, "processing.delete.confirm", _deletePackage); - } - - _deletePackage() async { - setState(() { - _isLoading = true; - }); - PackageModel packageModel = - Provider.of(context, listen: false); - try { - await packageModel.deletePackage(_package); - Navigator.pop(context, true); - } catch (e) { - showMsgDialog(context, "Error", e.toString()); - } finally { - setState(() { - _isLoading = false; - }); - } - } } diff --git a/lib/pages/processing/processing_info.dart b/lib/pages/processing/processing_info.dart index 38e740a..b3783b7 100644 --- a/lib/pages/processing/processing_info.dart +++ b/lib/pages/processing/processing_info.dart @@ -1,6 +1,7 @@ import 'package:fcs/domain/entities/package.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/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/display_text.dart'; @@ -8,6 +9,7 @@ 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/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:intl/intl.dart'; @@ -53,8 +55,6 @@ class _ProcessingInfoState extends State { @override Widget build(BuildContext context) { - bool isCustomer = Provider.of(context).isCustomer(); - final trackingIdBox = DisplayText( text: _package.trackingID, labelTextKey: "processing.tracking.id", @@ -65,11 +65,6 @@ class _ProcessingInfoState extends State { labelTextKey: "processing.name", iconData: Icons.perm_identity, ); - final statusBox = DisplayText( - text: _package.currentStatus, - labelTextKey: "processing.status", - iconData: AntDesign.exclamationcircleo, - ); final marketBox = DisplayText( text: _package.market ?? "-", labelTextKey: "processing.market", @@ -109,12 +104,14 @@ class _ProcessingInfoState extends State { color: primaryColor, ), actions: [ - isCustomer - ? Container() - : IconButton( - icon: Icon(Icons.edit, color: primaryColor), - onPressed: _gotoEditor, - ) + IconButton( + icon: Icon(Icons.delete, color: primaryColor), + onPressed: _delete, + ), + IconButton( + icon: Icon(Icons.edit, color: primaryColor), + onPressed: _gotoEditor, + ), ], ), body: Card( @@ -127,27 +124,12 @@ class _ProcessingInfoState extends State { trackingIdBox, customerNameBox, marketBox, - statusBox, _package.photoUrls.length == 0 ? Container() : img, descBox, remarkBox, - ExpansionTile( - initiallyExpanded: true, - title: Text( - 'Status', - style: TextStyle( - color: primaryColor, fontWeight: FontWeight.bold), - ), - children: [ - Container( - padding: EdgeInsets.only(left: 20), - height: 400, - child: Timeline( - children: _models(), - position: TimelinePosition.Left), - ), - ], - ), + StatusTree( + shipmentHistory: _package.shipmentHistory, + currentStatus: _package.currentStatus), SizedBox( height: 20, ) @@ -160,36 +142,26 @@ class _ProcessingInfoState extends State { ); } - List _models() { - if (_package.shipmentHistory == null) return []; - return _package.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)), - 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(); + _delete() { + showConfirmDialog(context, "processing.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 { diff --git a/lib/pages/processing/processing_list.dart b/lib/pages/processing/processing_list.dart index 6e34748..c6e79db 100644 --- a/lib/pages/processing/processing_list.dart +++ b/lib/pages/processing/processing_list.dart @@ -1,10 +1,8 @@ import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/localization/app_translations.dart'; import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/package/package_info.dart'; -import 'package:fcs/pages/package/package_new.dart'; import 'package:fcs/pages/package_search/package_serach.dart'; import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/local_text.dart'; @@ -13,6 +11,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'processing_info.dart'; import 'processing_list_row.dart'; class ProcessingList extends StatefulWidget { @@ -91,7 +90,7 @@ class _ProcessingListState extends State { if (_package == null) return; Navigator.push( context, - BottomUpPageRoute(PackageInfo(package: _package)), + BottomUpPageRoute(ProcessingInfo(package: _package)), ); } } diff --git a/lib/pages/receiving/receiving_new.dart b/lib/pages/receiving/receiving_editor.dart similarity index 77% rename from lib/pages/receiving/receiving_new.dart rename to lib/pages/receiving/receiving_editor.dart index 0a0af86..f51805a 100644 --- a/lib/pages/receiving/receiving_new.dart +++ b/lib/pages/receiving/receiving_editor.dart @@ -20,22 +20,38 @@ import 'package:provider/provider.dart'; typedef void FindCallBack(); -class ReceivingNew extends StatefulWidget { - const ReceivingNew(); +class ReceivingEditor extends StatefulWidget { + final Package package; + const ReceivingEditor({this.package}); @override - _ReceivingNewState createState() => _ReceivingNewState(); + _ReceivingEditorState createState() => _ReceivingEditorState(); } -class _ReceivingNewState extends State { +class _ReceivingEditorState extends State { + Package package = Package(); bool _isLoading = false; + bool _isNew; User user; - TextEditingController _transcationIDCtl = new TextEditingController(); + TextEditingController _trackingIDCtl = new TextEditingController(); TextEditingController _remarkCtl = new TextEditingController(); MultiImgController _multiImgController = MultiImgController(); @override void initState() { super.initState(); + _isNew = widget.package == null; + if (!_isNew) { + package = widget.package; + _trackingIDCtl.text = package.trackingID; + _remarkCtl.text = package.remark; + _multiImgController.setImageUrls = package.photoUrls; + user = User( + fcsID: package.fcsID, + name: package.userName, + phoneNumber: package.phoneNumber); + } else { + package = new Package(); + } } @override @@ -66,7 +82,7 @@ class _ReceivingNewState extends State { Expanded( child: InputText( labelTextKey: "receiving.tracking.id", - controller: _transcationIDCtl, + controller: _trackingIDCtl, )), IconButton( icon: Icon(MaterialCommunityIcons.barcode_scan, @@ -99,7 +115,13 @@ class _ReceivingNewState extends State { final createButton = fcsButton( context, getLocalString(context, 'receiving.create_btn'), - callack: _create, + callack: _save, + ); + + final updateButton = fcsButton( + context, + getLocalString(context, 'receiving.update_btn'), + callack: _save, ); return LocalProgress( @@ -115,7 +137,7 @@ class _ReceivingNewState extends State { backgroundColor: Colors.white, title: LocalText( context, - "receiving.new", + _isNew ? "receiving.new" : "receiving.update", fontSize: 20, color: primaryColor, ), @@ -139,7 +161,7 @@ class _ReceivingNewState extends State { SizedBox( height: 20, ), - createButton, + _isNew ? createButton : updateButton, SizedBox( height: 10, ), @@ -166,7 +188,7 @@ class _ReceivingNewState extends State { String barcode = await scanBarcode(); if (barcode != null) { setState(() { - _transcationIDCtl.text = barcode; + _trackingIDCtl.text = barcode; }); } } catch (e) { @@ -174,9 +196,8 @@ class _ReceivingNewState extends State { } } - _create() async { - Package package = Package(); - package.trackingID = _transcationIDCtl.text; + _save() async { + package.trackingID = _trackingIDCtl.text; package.remark = _remarkCtl.text; if (package.trackingID == null || package.trackingID == "") { @@ -189,8 +210,17 @@ class _ReceivingNewState extends State { PackageModel packageModel = Provider.of(context, listen: false); try { - await packageModel.createPackage(user, package, - _multiImgController.getAddedFile, _multiImgController.getDeletedUrl); + if (_isNew) { + await packageModel.createReceiving( + user, package, _multiImgController.getAddedFile); + } else { + package.id = widget.package.id; + await packageModel.updateReceiving( + user, + package, + _multiImgController.getAddedFile, + _multiImgController.getDeletedUrl); + } Navigator.pop(context); } catch (e) { showMsgDialog(context, "Error", e.toString()); diff --git a/lib/pages/receiving/receiving_info.dart b/lib/pages/receiving/receiving_info.dart index dbaac71..407abcb 100644 --- a/lib/pages/receiving/receiving_info.dart +++ b/lib/pages/receiving/receiving_info.dart @@ -18,6 +18,8 @@ import 'package:provider/provider.dart'; import 'package:timeline_list/timeline.dart'; import 'package:timeline_list/timeline_model.dart'; +import 'receiving_editor.dart'; + final DateFormat dateFormat = DateFormat("d MMM yyyy"); class ReceivingInfo extends StatefulWidget { @@ -40,9 +42,9 @@ class _ReceivingInfoState extends State { } initPackage(Package package) { + multiImgController.setImageUrls = package.photoUrls; setState(() { _package = package; - multiImgController.setImageUrls = package.photoUrls; }); } @@ -89,18 +91,22 @@ class _ReceivingInfoState extends State { backgroundColor: Colors.white, title: LocalText( context, - "package.info.title", + "receiving.info", fontSize: 20, color: primaryColor, ), - actions: [ - isCustomer - ? Container() - : IconButton( + actions: isCustomer + ? null + : [ + IconButton( icon: Icon(Icons.delete, color: primaryColor), onPressed: _delete, + ), + IconButton( + icon: Icon(Icons.edit, color: primaryColor), + onPressed: _edit, ) - ], + ], ), body: Card( child: Column( @@ -113,19 +119,9 @@ class _ReceivingInfoState extends State { _package.userID != null ? customerNameBox : Container(), _package.photoUrls.length == 0 ? Container() : img, remarkBox, - ExpansionTile( - initiallyExpanded: true, - title: Text( - 'Status', - style: TextStyle( - color: primaryColor, fontWeight: FontWeight.bold), - ), - children: [ - StatusTree( - shipmentHistory: _package.shipmentHistory, - currentStatus: _package.currentStatus), - ], - ), + StatusTree( + shipmentHistory: _package.shipmentHistory, + currentStatus: _package.currentStatus), SizedBox( height: 20, ) @@ -138,6 +134,19 @@ class _ReceivingInfoState extends State { ); } + _edit() async { + await Navigator.push( + context, + BottomUpPageRoute(ReceivingEditor( + package: widget.package, + )), + ); + PackageModel packageModel = + Provider.of(context, listen: false); + var pkg = await packageModel.getPackage(widget.package.id); + initPackage(pkg); + } + _delete() { showConfirmDialog(context, "receiving.delete.confirm", _deleteReceiving); } @@ -149,7 +158,7 @@ class _ReceivingInfoState extends State { try { PackageModel packageModel = Provider.of(context, listen: false); - await packageModel.deletePackage(_package); + await packageModel.deleteReceiving(_package); Navigator.pop(context); } catch (e) { showMsgDialog(context, "Error", e.toString()); diff --git a/lib/pages/receiving/receiving_list.dart b/lib/pages/receiving/receiving_list.dart index e145d9a..733f04a 100644 --- a/lib/pages/receiving/receiving_list.dart +++ b/lib/pages/receiving/receiving_list.dart @@ -12,7 +12,7 @@ import 'package:provider/provider.dart'; import 'receiving_info.dart'; import 'receiving_list_row.dart'; -import 'receiving_new.dart'; +import 'receiving_editor.dart'; class ReceivingList extends StatefulWidget { @override @@ -98,7 +98,7 @@ class _ReceivingListState extends State { _newReceiving() { Navigator.push( context, - BottomUpPageRoute(ReceivingNew()), + BottomUpPageRoute(ReceivingEditor()), ); } diff --git a/lib/pages/shipment/shipment_list.dart b/lib/pages/shipment/shipment_list.dart index 4df16c0..949f046 100644 --- a/lib/pages/shipment/shipment_list.dart +++ b/lib/pages/shipment/shipment_list.dart @@ -4,6 +4,7 @@ import 'package:fcs/pages/shipment/model/shipment_model.dart'; import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; @@ -40,7 +41,7 @@ class _ShipmentListState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(Icons.close), + icon: new Icon(CupertinoIcons.back), onPressed: () => Navigator.of(context).pop(), ), backgroundColor: primaryColor, @@ -72,7 +73,7 @@ class _ShipmentListState extends State { _newPickup(); }, icon: Icon(Icons.add), - label: Text(AppTranslations.of(context).text("shipment.new")), + label: LocalText(context, "shipment.new", color: Colors.white), backgroundColor: primaryColor, ), body: new ListView.separated( diff --git a/lib/pages/widgets/badge.dart b/lib/pages/widgets/badge.dart index be6006e..2018b2f 100644 --- a/lib/pages/widgets/badge.dart +++ b/lib/pages/widgets/badge.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; Widget badgeCounter(Widget child, int counter) { return Container( width: 120, - height: 140, child: new Stack( children: [ child, diff --git a/lib/pages/widgets/local_popup_menu_button.dart b/lib/pages/widgets/local_popup_menu_button.dart new file mode 100644 index 0000000..3132ac5 --- /dev/null +++ b/lib/pages/widgets/local_popup_menu_button.dart @@ -0,0 +1,120 @@ +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:flutter/material.dart'; + +import 'local_popupmenu.dart'; + +typedef PopupMenuCallback = Function(LocalPopupMenu popupMenu); + +class LocalPopupMenuButton extends StatefulWidget { + final PopupMenuCallback popupMenuCallback; + final List popmenus; + final bool multiSelect; + + const LocalPopupMenuButton( + {Key key, + this.popupMenuCallback, + this.popmenus, + this.multiSelect = false}) + : super(key: key); + + @override + _LocalPopupMenuButtonState createState() => _LocalPopupMenuButtonState(); +} + +class _LocalPopupMenuButtonState extends State { + List popmenus; + + @override + void initState() { + popmenus = widget.popmenus; + super.initState(); + } + + @override + Widget build(BuildContext context) { + bool hightlight = _needHighlight(); + return PopupMenuButton( + elevation: 3.2, + onSelected: (selected) { + if (!widget.multiSelect) { + setState(() { + popmenus.forEach((e) { + if (e.id != selected.id) + e.selected = false; + else + e.selected = true; + }); + }); + selected.selected = true; + } else { + setState(() { + popmenus.forEach((e) { + if (e.id == selected.id) e.selected = !e.selected; + }); + }); + selected.selected = !selected.selected; + } + if (widget.popupMenuCallback != null) + widget.popupMenuCallback(selected); + }, + icon: Container( + width: 30, + height: 30, + decoration: new BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + child: Stack( + fit: StackFit.expand, + children: [ + Icon( + Icons.filter_list, + color: primaryColor, + ), + hightlight + ? Positioned( + bottom: 0, + right: 0, + child: Container( + width: 10, + height: 10, + decoration: new BoxDecoration( + shape: BoxShape.circle, + color: secondaryColor, + ), + ), + ) + : Container() + ], + )), + itemBuilder: (BuildContext context) { + return popmenus.map((LocalPopupMenu choice) { + return PopupMenuItem( + value: choice, + child: Row( + children: [ + LocalText(context, choice.textKey, color: primaryColor), + SizedBox( + width: 10, + ), + choice.selected + ? Icon( + Icons.check, + color: Colors.grey, + ) + : Container(), + ], + ), + ); + }).toList(); + }); + } + + bool _needHighlight() { + popmenus.forEach((e) { + if (e.selected && e.highlight) return true; + }); + return false; + } +} diff --git a/lib/pages/widgets/local_popupmenu.dart b/lib/pages/widgets/local_popupmenu.dart new file mode 100644 index 0000000..7029c58 --- /dev/null +++ b/lib/pages/widgets/local_popupmenu.dart @@ -0,0 +1,8 @@ +class LocalPopupMenu { + int id; + String textKey; + bool selected; + bool highlight; + LocalPopupMenu( + {this.id, this.textKey, this.selected = false, this.highlight = false}); +} diff --git a/lib/pages/widgets/popupmenu.dart b/lib/pages/widgets/popupmenu.dart index 7cc6380..c95d348 100644 --- a/lib/pages/widgets/popupmenu.dart +++ b/lib/pages/widgets/popupmenu.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - class PopupMenu { int id; String status; diff --git a/lib/pages/widgets/status_tree.dart b/lib/pages/widgets/status_tree.dart index fb89a54..234cba3 100644 --- a/lib/pages/widgets/status_tree.dart +++ b/lib/pages/widgets/status_tree.dart @@ -19,10 +19,19 @@ class StatusTree extends StatelessWidget { : super(key: key); @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(left: 20), - height: 400, - child: Timeline(children: _models(), position: TimelinePosition.Left), + return ExpansionTile( + initiallyExpanded: true, + title: Text( + 'Status', + style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold), + ), + children: [ + Container( + padding: EdgeInsets.only(left: 20), + height: 400, + child: Timeline(children: _models(), position: TimelinePosition.Left), + ) + ], ); } diff --git a/lib/pages/widgets/task_button.dart b/lib/pages/widgets/task_button.dart index 07037ce..5ad828c 100644 --- a/lib/pages/widgets/task_button.dart +++ b/lib/pages/widgets/task_button.dart @@ -1,3 +1,4 @@ +import 'package:fcs/helpers/theme.dart'; import 'package:fcs/localization/app_translations.dart'; import 'package:fcs/pages/main/model/language_model.dart'; import 'package:flutter/material.dart'; @@ -22,7 +23,7 @@ class TaskButton extends StatelessWidget { onTap: btnCallback != null ? btnCallback : () => {}, child: Container( width: 120, - height: 170, + height: 155, padding: EdgeInsets.only(top: 0.0, left: 5, right: 5), decoration: new BoxDecoration( color: Colors.transparent, @@ -42,7 +43,7 @@ class TaskButton extends StatelessWidget { ), ), Container( - height: 60, + height: 45, alignment: Alignment.topCenter, child: Text(AppTranslations.of(context).text(titleKey), textAlign: TextAlign.center,