diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 7dd65ed..3d46287 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -105,7 +105,15 @@ "package.create.packages":"Complete receiving", "package.create.market":"Market", - "package.edit.title":"PACKAGE", + "package.edit.status":"Status", + "package.edit.title":"Edit Package", + "package.edit.remark":"Remark", + "package.edit.desc":"Description", + "package.edit.complete.process.btn":"Complete processing", + "package.edit.procseeing":"Processing", + + "package.info.title":"Package", + "package.arrival.date":"Arrival Date", "package.number":"Box Number", "package.rate":"Rate", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index bb1b6e4..a7cbe9f 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -104,10 +104,17 @@ "package.create.phone":"ဖုန်းနံပါတ်", "package.tracking.id":"Tracking ID", "package.create.packages":"အထုပ် အသစ်များ လက်ခံမည်", - "package.create.market":"Market", + "package.create.market":"အွန်လိုင်စျေးဆိုင်", + + "package.edit.title":"အထုပ် ပြင်ဆင်ခြင်း", + "package.edit.remark":"မှတ်ချက်", + "package.edit.desc":"ဖော်ပြချက်", + "package.edit.complete.process.btn":"မွမ်းမံခြင်း ပြီးဆုံးသည်", + "package.edit.status":"အခြေအနေ", + "package.edit.procseeing":"မွမ်းမံခြင်း", + + "package.info.title":"အထုပ်", - "package.new":"New Package", - "package.edit.title":"PACKAGE", "package.arrival.date":"Arrival Date", "package.number":"Package Number", "package.rate":"Rate", diff --git a/lib/fcs/common/data/providers/auth_fb.dart b/lib/fcs/common/data/providers/auth_fb.dart index 26148ca..48a8df2 100644 --- a/lib/fcs/common/data/providers/auth_fb.dart +++ b/lib/fcs/common/data/providers/auth_fb.dart @@ -126,19 +126,22 @@ class AuthFb { log.info("Claims:${idToken.claims}"); User user = User(); - user.id = firebaseUser.uid; - user.status = idToken.claims["status"]; + user.status = idToken.claims["st"]; user.phoneNumber = firebaseUser.phoneNumber; // add privileges - String privileges = idToken.claims["privileges"]; + String privileges = idToken.claims["pr"]; if (privileges != null && privileges != "") { user.privileges = privileges.split(":").toList(); } - User _user = await getUserFromFirestore(user.id); - if (_user != null) { - user.fcsID = _user.fcsID; - user.name = _user.name; + String cid = idToken.claims["cid"]; + if (cid != null && cid != "") { + User _user = await getUserFromFirestore(cid); + if (_user != null) { + user.id = cid; + user.fcsID = _user.fcsID; + user.name = _user.name; + } } return user; } @@ -170,6 +173,16 @@ class AuthFb { return getUser(refreshIdToken: true); } + Future joinInvite(String userName) async { + await requestAPI("/join_invite", "POST", + payload: { + 'user_name': userName, + }, + token: await getToken()); + // refresh token once signup + return getUser(refreshIdToken: true); + } + Future hasInvite() async { var invited = await requestAPI("/check_invitation", "GET", token: await getToken()); @@ -210,4 +223,17 @@ class AuthFb { yield setting; } } + + Stream user(String userID) async* { + Stream snapshot = Firestore.instance + .collection(user_collection) + .document(userID) + .snapshots(); + + await for (var snap in snapshot) { + User user = User.fromMap(snap.data, snap.documentID); + user = await getUser(refreshIdToken: true); + yield user; + } + } } diff --git a/lib/fcs/common/data/providers/user_data_provider.dart b/lib/fcs/common/data/providers/user_data_provider.dart index 721e15c..2e5d02d 100644 --- a/lib/fcs/common/data/providers/user_data_provider.dart +++ b/lib/fcs/common/data/providers/user_data_provider.dart @@ -23,7 +23,7 @@ class UserDataProvider { } Future acceptRequest(String userID) async { - return await requestAPI("/invites", "PUT", + return await requestAPI("/accept_request", "PUT", payload: {"id": userID}, token: await getToken()); } diff --git a/lib/fcs/common/domain/constants.dart b/lib/fcs/common/domain/constants.dart index b56cec0..bd66e68 100644 --- a/lib/fcs/common/domain/constants.dart +++ b/lib/fcs/common/domain/constants.dart @@ -6,6 +6,11 @@ const privilege_collection = "privileges"; const markets_collection = "markets"; const packages_collection = "packages"; +const user_requested_status = "requested"; +const user_invited_status = "invited"; + +const pkg_files_path = "/packages"; + const ok_doc_id = "ok"; const biz_collection = "bizs"; diff --git a/lib/fcs/common/domain/entities/package.dart b/lib/fcs/common/domain/entities/package.dart index 7c14a4c..5424132 100644 --- a/lib/fcs/common/domain/entities/package.dart +++ b/lib/fcs/common/domain/entities/package.dart @@ -10,8 +10,9 @@ class Package { String phoneNumber; String currentStatus; DateTime currentStatusDate; - List> allStatus; List photoUrls; + List shipmentHistory; + String desc; String status; String shipmentNumber; @@ -39,8 +40,6 @@ class Package { shipmentNumber + "-" + receiverNumber + " #" + boxNumber; double get price => rate.toDouble() * weight; - List shipmentHistory; - Package({ this.id, this.trackingID, @@ -66,20 +65,20 @@ class Package { this.cargoDesc, this.market, this.shipmentHistory, - this.allStatus, this.currentStatus, this.currentStatusDate, this.photoUrls, + this.desc, }); factory Package.fromMap(Map map, String docID) { var _currentStatusDate = (map['current_status_date'] as Timestamp); - List> _allStatus = List.from(map['all_status']) - .map((e) => Map.from(e)) + List _shipmentStatus = List.from(map['all_status']) + .map((e) => ShipmentStatus.fromMap(Map.from(e))) .toList(); List _photoUrls = - map['photo_urls'] == null ? [] : map['photo_urls'].cast>(); + map['photo_urls'] == null ? [] : List.from(map['photo_urls']); return Package( id: docID, @@ -89,13 +88,22 @@ class Package { market: map['market'], userName: map['user_name'], phoneNumber: map['phone_number'], + remark: map['remark'], + desc: map['desc'], currentStatus: map['current_status'], currentStatusDate: _currentStatusDate != null ? _currentStatusDate.toDate() : null, photoUrls: _photoUrls, - allStatus: _allStatus); + shipmentHistory: _shipmentStatus); } - Map toJson() => - {'id': id, 'tracking_id': trackingID, 'market': market, 'fcs_id': fcsID}; + Map toJson() => { + 'id': id, + 'tracking_id': trackingID, + 'market': market, + 'fcs_id': fcsID, + "desc": desc, + "remark": remark, + "photo_urls": photoUrls + }; } diff --git a/lib/fcs/common/domain/entities/user.dart b/lib/fcs/common/domain/entities/user.dart index 07803ed..141f376 100644 --- a/lib/fcs/common/domain/entities/user.dart +++ b/lib/fcs/common/domain/entities/user.dart @@ -94,6 +94,7 @@ class User { bool hasPackages() { return hasSysAdmin() || hasAdmin() || + status == userStatusJoined || (privileges != null ? privileges.contains('p') : false); } diff --git a/lib/fcs/common/domain/vo/shipment_status.dart b/lib/fcs/common/domain/vo/shipment_status.dart index 872f737..7180de7 100644 --- a/lib/fcs/common/domain/vo/shipment_status.dart +++ b/lib/fcs/common/domain/vo/shipment_status.dart @@ -1,6 +1,17 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + class ShipmentStatus { String status; DateTime date; bool done; ShipmentStatus({this.status, this.date, this.done}); + + factory ShipmentStatus.fromMap(Map map) { + var _date = (map['date'] as Timestamp); + return ShipmentStatus( + status: map['status'], + date: _date == null ? null : _date.toDate(), + done: map['done'], + ); + } } diff --git a/lib/fcs/common/helpers/firebase_helper.dart b/lib/fcs/common/helpers/firebase_helper.dart index 883cbf3..bf72b7f 100644 --- a/lib/fcs/common/helpers/firebase_helper.dart +++ b/lib/fcs/common/helpers/firebase_helper.dart @@ -1,5 +1,9 @@ +import 'dart:io'; + import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_storage/firebase_storage.dart'; import 'package:logging/logging.dart'; +import 'package:uuid/uuid.dart'; final log = Logger('firebaseHelper'); @@ -10,3 +14,19 @@ Future getToken() async { IdTokenResult token = await firebaseUser.getIdToken(); return token.token; } + +Future uploadStorage(String path, File file, {String fileName}) async { + if (fileName == null) { + fileName = Uuid().v4(); + } + StorageReference storageReference = + FirebaseStorage.instance.ref().child('$path/$fileName'); + StorageUploadTask uploadTask = storageReference.putFile(file); + await uploadTask.onComplete; + String downloadUrl = await storageReference.getDownloadURL(); + print("name:${await storageReference.getName()}"); + print("bucket:${await storageReference.getBucket()}"); + print("path:${await storageReference.getPath()}"); + print("meta:${await storageReference.getMetadata()}"); + return downloadUrl; +} diff --git a/lib/fcs/common/pages/customers/customer_list.dart b/lib/fcs/common/pages/customers/customer_list.dart index c0978f5..00c2568 100644 --- a/lib/fcs/common/pages/customers/customer_list.dart +++ b/lib/fcs/common/pages/customers/customer_list.dart @@ -1,3 +1,4 @@ +import 'package:fcs/fcs/common/domain/constants.dart'; import 'package:fcs/fcs/common/domain/entities/user.dart'; import 'package:fcs/fcs/common/helpers/theme.dart'; import 'package:fcs/fcs/common/pages/customers/customer_editor.dart'; @@ -12,6 +13,7 @@ import 'package:flutter_icons/flutter_icons.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'invitation_create.dart'; import 'invitation_editor.dart'; class CustomerList extends StatefulWidget { @@ -55,25 +57,17 @@ class _CustomerListState extends State { fontSize: 20, ), ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + Navigator.of(context).push(BottomUpPageRoute(InvitationCreate())); + }, + icon: Icon(Icons.add), + label: LocalText(context, "invitation.new", color: Colors.white), + backgroundColor: primaryColor, + ), body: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Padding( - padding: const EdgeInsets.only(top: 3, right: 5), - child: InkWell( - onTap: _invitations, - child: Container( - color: primaryColor, - child: Padding( - padding: const EdgeInsets.all(5.0), - child: Text( - "Invitations", - style: TextStyle(color: Colors.white), - ), - ), - ), - ), - ), Expanded( child: ListView.separated( separatorBuilder: (context, index) => Divider( @@ -152,7 +146,9 @@ class _CustomerListState extends State { Widget _status(String status) { return Text( - status == "requested" ? status : "", + (user_requested_status == status || user_invited_status == status) + ? status + : "", style: TextStyle(color: primaryColor, fontSize: 14), ); } diff --git a/lib/fcs/common/pages/model/main_model.dart b/lib/fcs/common/pages/model/main_model.dart index 85bc6fb..37169c8 100644 --- a/lib/fcs/common/pages/model/main_model.dart +++ b/lib/fcs/common/pages/model/main_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:fcs/fcs/common/domain/entities/auth_result.dart'; import 'package:fcs/fcs/common/domain/entities/auth_status.dart'; @@ -34,8 +35,10 @@ class MainModel extends ChangeNotifier { this.isOnline = _isOnline; notifyListeners(); }); - Services.instance.authService.onAuthStatus().listen((event) { + Services.instance.authService.onAuthStatus().listen((event) async { this.user = event; + this.user = + await Services.instance.authService.getUser(refreshIdToken: true); _initUser(user); notifyListeners(); }); @@ -74,7 +77,7 @@ class MainModel extends ChangeNotifier { this.isFirstLaunch = await SharedPref.isFirstLaunch(); this.isFirstLaunch = this.isFirstLaunch ?? true; - _loadUser(); + // _loadUser(); this.packageInfo = await PackageInfo.fromPlatform(); } @@ -82,9 +85,25 @@ class MainModel extends ChangeNotifier { models.add(model); } - void _initUser(User user) { - models.forEach((m) => m.initUser(user)); + StreamSubscription userListener; + void _initUser(User user) { + if (user != null) { + if (user.id != null && user.id != "") { + if (userListener != null) userListener.cancel(); + userListener = Services.instance.authService + .getUserStream(user.id) + .listen((event) { + this.user = event; + models.forEach((m) => m.initUser(user)); + notifyListeners(); + }); + } + models.forEach((m) => m.initUser(user)); + } else { + models.forEach((m) => m.logout()); + } + isLoaded = true; // if (firebaseMessaging != null) { // firebaseMessaging.subscribeToTopic(user.docID); // } @@ -104,15 +123,15 @@ class MainModel extends ChangeNotifier { } finally {} } - void _loadUser() async { - try { - this.user = await Services.instance.authService.getUser(); - _initUser(user); - } finally { - this.isLoaded = true; - notifyListeners(); - } - } + // void _loadUser() async { + // try { + // this.user = await Services.instance.authService.getUser(); + // _initUser(user); + // } finally { + // this.isLoaded = true; + // notifyListeners(); + // } + // } @override void dispose() { @@ -162,6 +181,13 @@ class MainModel extends ChangeNotifier { notifyListeners(); } + Future joinInvite(String userName) async { + await Services.instance.authService.joinInvite(userName); + this.user = + await Services.instance.authService.getUser(refreshIdToken: true); + notifyListeners(); + } + Future updateProfile(String newUserName) async { await Services.instance.authService.updateProfile(newUserName); this.user = diff --git a/lib/fcs/common/pages/package/buyer_info.dart b/lib/fcs/common/pages/package/buyer_info.dart deleted file mode 100644 index ade9c23..0000000 --- a/lib/fcs/common/pages/package/buyer_info.dart +++ /dev/null @@ -1,279 +0,0 @@ -import 'package:fcs/fcs/common/helpers/theme.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; -import 'package:fcs/model/buyer_model.dart'; -import 'package:fcs/model/main_model.dart'; -import 'package:fcs/pages/quota_page.dart'; -import 'package:fcs/fcs/common/pages/util.dart'; -import 'package:fcs/util.dart'; -import 'package:fcs/vo/buyer.dart'; -import 'package:fcs/widget/label_widgets.dart'; -import 'package:fcs/widget/localization/app_translations.dart'; -import 'package:fcs/widget/progress.dart'; - -class BuyerInfo extends StatefulWidget { - final Buyer buyer; - const BuyerInfo({this.buyer}); - @override - _BuyerInfoState createState() => _BuyerInfoState(); -} - -class _BuyerInfoState extends State { - var dateFormatter = new DateFormat('dd MMM yyyy - hh:mm a'); - TextEditingController _companyName = new TextEditingController(); - TextEditingController _comAddress = new TextEditingController(); - TextEditingController _numOfShops = new TextEditingController(); - TextEditingController _bizType = new TextEditingController(); - TextEditingController _accountName = new TextEditingController(); - TextEditingController _accountNumber = new TextEditingController(); - - bool _isLoading = false; - Buyer buyer; - - @override - void initState() { - super.initState(); - if (widget.buyer != null) { - buyer = widget.buyer; - Provider.of(context, listen: false) - .loadBuyerProducts(buyer) - .then((b) { - if (mounted) { - setState(() { - buyer = b; - }); - } - }); - } - } - - @override - Widget build(BuildContext context) { - var mainModel = Provider.of(context); - - _companyName.text = buyer.bizName; - _comAddress.text = buyer.bizAddress; - _numOfShops.text = buyer.numOfShops.toString(); - _bizType.text = buyer.bizType; - _accountName.text = buyer.userName; - _accountNumber.text = buyer.userID; - - final dateBox = - labeledText(context, dateFormatter.format(buyer.regDate), "reg.date"); - final accountBox = - labeledText(context, buyer.userName, "buyer.account_name"); - final phoneBox = labeledText(context, buyer.phone, "buyer.phone_number"); - final statusBox = labeledText(context, buyer.status, "reg.status"); - final bizNameBox = labeledText(context, _companyName.text, "reg.biz_name"); - final bizAddressBox = - labeledText(context, _comAddress.text, "reg.biz_address"); - final shopNumberBox = - labeledText(context, _numOfShops.text, "reg.biz_shops"); - final typeBox = labeledText(context, _bizType.text, "buyer.type_biz"); - final dailyQuotaBox = labeledText( - context, formatNumber(buyer.dailyQuota), "reg.quota", - number: true); - final dailyQuotaUsedBox = labeledText( - context, formatNumber(buyer.dailyQuotaUsed), "reg.quota.used", - number: true); - final maxQuotaBox = labeledText( - context, formatNumber(buyer.maxQuota), "reg.max_quota", - number: true); - final maxQuotaUsedBox = labeledText( - context, formatNumber(buyer.maxQuotaUsed), "reg.max_quota.used", - number: true); - final nricFrontBox = - labeledImg(context, buyer.nricFrontUrl, "reg_info.nric_front"); - final nricBackBox = - labeledImg(context, buyer.nricBackUrl, "reg_info.nric_back"); - - return LocalProgress( - inAsyncCall: _isLoading, - child: Scaffold( - appBar: AppBar( - backgroundColor: primaryColor, - title: Text(AppTranslations.of(context).text("buyer.title")), - actions: [ - mainModel.showHistoryBtn() - ? IconButton( - icon: Icon(Icons.history), - onPressed: () { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => - // DocumentLogPage(docID: buyer.id)), - // ); - }, - ) - : Container(), - PopupMenuButton( - onSelected: (s) { - if (s == 1) { - showConfirmDialog(context, "buyer.delete.confirm", () { - _delete(); - }); - } else if (s == 2) { - showConfirmDialog(context, "buyer.approve.confirm", () { - _approve(); - }); - } else if (s == 3) { - showCommentDialog(context, (comment) { - _reject(comment); - }); - } else if (s == 4) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => QuotaPage( - buyer: this.buyer, - isApproved: true, - )), - ); - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: 1, - child: Text("Delete"), - ), - PopupMenuItem( - enabled: buyer.isPending(), - value: 2, - child: Text("Approve"), - ), - PopupMenuItem( - enabled: buyer.isPending(), - value: 3, - child: Text("Reject"), - ), - PopupMenuItem( - enabled: buyer.isApproved(), - value: 4, - child: Text("Allocate Quota"), - ), - ], - ), - ], - ), - body: Container( - padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10), - child: ListView( - children: [ - dateBox, - Divider(), - accountBox, - Divider(), - Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: phoneBox, - ), - InkWell( - onTap: () => call(context, buyer.phone), - child: Icon( - Icons.open_in_new, - color: Colors.grey, - size: 15, - ), - ), - ], - ), - Divider(), - statusBox, - Divider(), - bizNameBox, - Divider(), - bizAddressBox, - Divider(), - typeBox, - Divider(), - dailyQuotaBox, - Divider(), - dailyQuotaUsedBox, - Divider(), - maxQuotaBox, - Divider(), - maxQuotaUsedBox, - Divider(), - nricFrontBox, - Divider(), - nricBackBox - ], - ), - ), - ), - ); - } - - _delete() async { - setState(() { - _isLoading = true; - }); - - try { - await Provider.of(context).delete(buyer); - Navigator.pop(context, true); - } catch (e) { - showMsgDialog(context, "Error", e.toString()); - } finally { - setState(() { - _isLoading = false; - }); - } - } - - _approve() async { - var _buyer = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => QuotaPage( - buyer: this.buyer, - isApproved: false, - )), - ); - if (_buyer == null) return; - - setState(() { - _isLoading = true; - }); - - try { - this.buyer.dailyQuota = _buyer.dailyQuota; - this.buyer.maxQuota = _buyer.maxQuota; - await Provider.of(context).approve(this.buyer); - Navigator.pop(context, true); - } catch (e) { - showMsgDialog(context, "Error", e.toString()); - } finally { - setState(() { - _isLoading = false; - }); - } - } - - _reject(comment) async { - if (comment == null || comment == "") { - showMsgDialog(context, "Error", "Please enter comment!"); - return; - } - buyer.comment = comment; - setState(() { - _isLoading = true; - }); - - try { - await Provider.of(context).reject(buyer); - Navigator.pop(context, true); - } catch (e) { - showMsgDialog(context, "Error", e.toString()); - } finally { - setState(() { - _isLoading = false; - }); - } - } -} diff --git a/lib/fcs/common/pages/package/buyer_list_row.dart b/lib/fcs/common/pages/package/buyer_list_row.dart deleted file mode 100644 index 2ebdb90..0000000 --- a/lib/fcs/common/pages/package/buyer_list_row.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:fcs/fcs/common/pages/package/buyer_info.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:fcs/model/buyer_model.dart'; -import 'package:fcs/fcs/common/pages/util.dart'; -import 'package:fcs/fcs/common/helpers/theme.dart'; -import 'package:fcs/vo/buyer.dart'; - -class BuyerListRow extends StatefulWidget { - final Buyer buyer; - const BuyerListRow({this.buyer}); - - @override - _BuyerListRowState createState() => _BuyerListRowState(); -} - -class _BuyerListRowState extends State { - final double dotSize = 15.0; - Buyer _buyer = new Buyer(); - - @override - void initState() { - super.initState(); - BuyerModel buyerModel = Provider.of(context, listen: false); - if (widget.buyer != null) { - buyerModel.buyers.forEach((b) { - if (widget.buyer.id == b.id) { - _buyer = b; - } - }); - } - } - - @override - Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(left: 15, right: 15), - child: Card( - elevation: 10, - color: Colors.white, - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => BuyerInfo(buyer: _buyer)), - ); - }, - child: Row( - children: [ - Expanded( - child: new Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: new Row( - children: [ - new Padding( - padding: new EdgeInsets.symmetric( - horizontal: 32.0 - dotSize / 2), - child: Image.asset( - "assets/buyer.png", - width: 40, - height: 40, - color: primaryColor, - ), - ), - new Expanded( - child: new Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - new Text( - _buyer.userName == null ? '' : _buyer.userName, - style: new TextStyle( - fontSize: 15.0, color: Colors.black), - ), - new Text( - _buyer.bizName == null ? "" : _buyer.bizName, - style: new TextStyle( - fontSize: 13.0, color: Colors.grey), - ), - new Text( - _buyer.phone == null ? "" : _buyer.phone, - style: new TextStyle( - fontSize: 13.0, color: Colors.grey), - ), - ], - ), - ), - ], - ), - ), - ), - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: getStatus(_buyer.status), - ), - ], - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/fcs/common/pages/package/model/package_model.dart b/lib/fcs/common/pages/package/model/package_model.dart index e33c57d..de52f95 100644 --- a/lib/fcs/common/pages/package/model/package_model.dart +++ b/lib/fcs/common/pages/package/model/package_model.dart @@ -1,9 +1,12 @@ import 'dart:async'; +import 'dart:io'; +import 'package:path/path.dart' as Path; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/fcs/common/domain/constants.dart'; import 'package:fcs/fcs/common/domain/entities/package.dart'; import 'package:fcs/fcs/common/domain/entities/user.dart'; +import 'package:fcs/fcs/common/helpers/firebase_helper.dart'; import 'package:fcs/fcs/common/pages/model/base_model.dart'; import 'package:fcs/fcs/common/services/services.dart'; import 'package:logging/logging.dart'; @@ -33,12 +36,16 @@ class PackageModel extends BaseModel { } else { path = "/$packages_collection"; } + if (listener != null) listener.cancel(); + packages = []; + try { listener = Firestore.instance .collection("$path") .where("is_delivered", isEqualTo: false) .snapshots() .listen((QuerySnapshot snapshot) { + print("reloaded...."); packages.clear(); packages = snapshot.documents.map((documentSnapshot) { var package = Package.fromMap( @@ -60,4 +67,20 @@ class PackageModel extends BaseModel { return Services.instance.packageService .createPackages(packages, user.fcsID); } + + Future completeProcessing( + Package package, List files, List deletedUrls) async { + 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 url = await uploadStorage(path, f); + package.photoUrls.add(url); + } + package.photoUrls.removeWhere((e) => deletedUrls.contains(e)); + } + await request("/packages", "PUT", + payload: package.toJson(), token: await getToken()); + } } diff --git a/lib/fcs/common/pages/package/package_creation.dart b/lib/fcs/common/pages/package/package_creation.dart deleted file mode 100644 index 06628c1..0000000 --- a/lib/fcs/common/pages/package/package_creation.dart +++ /dev/null @@ -1,550 +0,0 @@ -import 'package:fcs/fcs/common/domain/entities/package.dart'; -import 'package:fcs/fcs/common/domain/vo/shipping_address.dart'; -import 'package:fcs/fcs/common/helpers/theme.dart'; -import 'package:fcs/fcs/common/localization/app_translations.dart'; -import 'package:fcs/fcs/common/pages/model/main_model.dart'; -import 'package:fcs/fcs/common/pages/package/tracking_id_page.dart'; -import 'package:fcs/fcs/common/pages/package/shipping_address_editor.dart'; -import 'package:fcs/fcs/common/pages/package/shipping_address_list.dart'; -import 'package:fcs/fcs/common/pages/package/shipping_address_row.dart'; -import 'package:fcs/fcs/common/pages/util.dart'; -import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; -import 'package:fcs/fcs/common/pages/widgets/fcs_expansion_tile.dart'; -import 'package:fcs/fcs/common/pages/widgets/my_data_table.dart'; -import 'package:fcs/fcs/common/pages/widgets/progress.dart'; -import 'package:fcs/pages/barcode_screen_page.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'; - -class PackageCreation extends StatefulWidget { - final Package package; - PackageCreation({this.package}); - - @override - _PackageCreationState createState() => _PackageCreationState(); -} - -class _PackageCreationState extends State { - TextEditingController _addressEditingController = new TextEditingController(); - TextEditingController _fromTimeEditingController = - new TextEditingController(); - TextEditingController _toTimeEditingController = new TextEditingController(); - TextEditingController _noOfPackageEditingController = - new TextEditingController(); - TextEditingController _weightEditingController = new TextEditingController(); - - Package _package; - bool _isLoading = false; - List _images = [ - "assets/photos/1.jpg", - "assets/photos/2.jpg", - "assets/photos/3.jpg" - ]; - bool isNew; - ShippingAddress shippingAddress = ShippingAddress( - fullName: 'U Nyi Nyi', - addressLine1: '154-19 64th Ave.', - addressLine2: 'Flushing', - city: 'NY', - state: 'NY', - phoneNumber: '+1 (292)215-2247'); - - @override - void initState() { - super.initState(); - if (widget.package != null) { - _package = widget.package; - isNew = false; - // _addressEditingController.text = _pickUp.address; - // _fromTimeEditingController.text = _pickUp.fromTime; - // _toTimeEditingController.text = _pickUp.toTime; - // _noOfPackageEditingController.text = _pickUp.numberOfPackage.toString(); - // _weightEditingController.text = _pickUp.weight.toString(); - } else { - isNew = true; - _package = Package(rate: 0, weight: 0); - } - } - - @override - void dispose() { - super.dispose(); - } - - final DateFormat dateFormat = DateFormat("d MMM yyyy"); - - List _models() { - print('_package.statusHistory=> ${_package.shipmentHistory}'); - 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)), - e.status == "Processed" - ? Text("(Waiting for payment)", - style: TextStyle( - color: e.done ? primaryColor : Colors.grey, - fontSize: 14, - fontWeight: FontWeight.bold)) - : Container(), - Text(dateFormat.format(e.date)), - ], - ), - ), - iconBackground: e.done ? primaryColor : Colors.grey, - icon: Icon( - e.status == "Shipped" - ? Ionicons.ios_airplane - : e.status == "Delivered" - ? MaterialCommunityIcons.truck_fast - : e.status == "Processed" - ? MaterialIcons.check - : Octicons.package, - color: Colors.white, - ))) - .toList(); - } - - @override - Widget build(BuildContext context) { - var owner = Provider.of(context).user.hasPackages(); - - final trackingIdBox = Padding( - padding: const EdgeInsets.only(left: 20.0, right: 20), - child: TextFormField( - initialValue: isNew ? "" : "zdf-sdfl-37sdfks", - decoration: InputDecoration( - fillColor: Colors.white, - labelText: 'Tracking ID', - hintText: 'Tracking ID', - filled: true, - suffixIcon: IconButton( - icon: Icon( - Ionicons.ios_barcode, - color: primaryColor, - ), - onPressed: () { - Navigator.push( - context, - BottomUpPageRoute(BarcodeScreenPage()), - ); - }), - icon: Icon(Octicons.package, color: primaryColor), - ), - ), - ); - var images = isNew ? [] : _images; - return LocalProgress( - inAsyncCall: _isLoading, - child: Scaffold( - appBar: AppBar( - centerTitle: true, - leading: new IconButton( - icon: new Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(), - ), - backgroundColor: primaryColor, - title: Text(AppTranslations.of(context).text("package.create.title")), - ), - body: Card( - child: Column( - children: [ - isNew ? Container() : Center(child: nameWidget(_package.market)), - Expanded( - child: ListView( - children: [ - owner - ? FcsExpansionTile( - title: Text( - 'Receiving', - style: TextStyle( - color: primaryColor, - fontWeight: FontWeight.bold), - ), - children: [ - trackingIdBox, - Padding( - padding: const EdgeInsets.only( - left: 20.0, right: 20), - child: TextFormField( - initialValue: isNew ? "" : "FCS-0203-390-2", - decoration: InputDecoration( - fillColor: Colors.white, - labelText: 'FCS_ID', - hintText: 'FCS_ID', - filled: true, - icon: Icon(Feather.user, - color: primaryColor), - suffixIcon: IconButton( - icon: Icon(Icons.search), - onPressed: () {})), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 20.0, right: 20), - child: TextFormField( - initialValue: _package.receiverName, - decoration: InputDecoration( - fillColor: Colors.white, - labelText: 'Customer Name', - filled: true, - icon: Icon(Feather.user, - color: Colors.white), - suffixIcon: IconButton( - icon: Icon(Icons.search), - onPressed: () {})), - ), - ), - ], - ) - : Container(), - owner - ? isNew - ? Container() - : ExpansionTile( - title: Text( - 'Processing', - style: TextStyle( - color: primaryColor, - fontWeight: FontWeight.bold), - ), - children: [ - Padding( - padding: const EdgeInsets.only( - left: 20.0, right: 20), - child: TextFormField( - initialValue: isNew - ? "" - : _package.cargoDesc.toString(), - decoration: InputDecoration( - fillColor: Colors.white, - labelText: 'Description', - filled: true, - icon: Icon(MaterialIcons.description, - color: primaryColor), - )), - ), - Padding( - padding: const EdgeInsets.only( - left: 20.0, right: 20), - child: fcsInput( - "Remark", - MaterialCommunityIcons.note, - ), - ), - SizedBox(height: 5), - ], - ) - : Container(), - isNew - ? Container() - : ExpansionTile( - title: Text( - 'Photos', - style: TextStyle( - color: primaryColor, - fontWeight: FontWeight.bold), - ), - children: [ - Container( - height: 130, - width: 500, - child: ListView.separated( - separatorBuilder: (context, index) => Divider( - color: Colors.black, - ), - itemCount: images.length + 1, - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - if (index == images.length) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - width: 200, - height: 70, - decoration: BoxDecoration( - border: Border.all( - color: primaryColor, - width: 2.0, - ), - ), - child: Icon(SimpleLineIcons.plus), - ), - ); - } else { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - width: 200, - height: 70, - decoration: BoxDecoration( - border: Border.all( - color: primaryColor, - width: 2.0, - ), - ), - child: Image.asset(images[index], - width: 50, height: 50), - ), - ); - } - }, - ), - ), - ], - ), - isNew ? Container() : getShippingAddressList(context), - isNew - ? Container() - : ExpansionTile( - title: Text( - 'Status', - style: TextStyle( - color: primaryColor, - fontWeight: FontWeight.bold), - ), - children: [ - Container( - height: 500, - padding: EdgeInsets.only(left: 20), - child: isNew - ? Container() - : Timeline( - children: _models(), - position: TimelinePosition.Left), - ), - ], - ), - ], - ), - ), - owner - ? widget.package == null - ? Align( - alignment: Alignment.bottomCenter, - child: Center( - child: Container( - width: 250, - child: FlatButton( - child: Text('Complete receiving'), - color: primaryColor, - textColor: Colors.white, - onPressed: () { - Navigator.pop(context); - }, - ), - ))) - : Container( - child: Column( - children: [ - Align( - alignment: Alignment.bottomCenter, - child: Center( - child: Container( - width: 250, - child: FlatButton( - child: Text('Complete processing'), - color: primaryColor, - textColor: Colors.white, - onPressed: () { - Navigator.pop(context); - }, - ), - ))), - ], - )) - : Container() - ], - ), - ), - ), - ); - } - - Widget getShippingAddressList(BuildContext context) { - return Container( - child: ExpansionTile( - title: Text( - "Shipping Addresses", - style: TextStyle( - fontWeight: FontWeight.bold, - fontStyle: FontStyle.normal, - color: primaryColor), - ), - children: [ - // Column( - // children: getAddressList(context, shipmentModel.shippingAddresses), - // ), - Container( - padding: EdgeInsets.only(left: 10, right: 10), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: new Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: Row( - children: [ - new Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - shippingAddress.fullName == null - ? '' - : shippingAddress.fullName, - style: new TextStyle( - fontSize: 15.0, - color: Colors.black, - fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - shippingAddress.addressLine1 == null - ? '' - : shippingAddress.addressLine1, - style: new TextStyle( - fontSize: 14.0, color: Colors.grey), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - shippingAddress.addressLine2 == null - ? '' - : shippingAddress.addressLine2, - style: new TextStyle( - fontSize: 14.0, color: Colors.grey), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - shippingAddress.city == null - ? '' - : shippingAddress.city, - style: new TextStyle( - fontSize: 14.0, color: Colors.grey), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - shippingAddress.state == null - ? '' - : shippingAddress.state, - style: new TextStyle( - fontSize: 14.0, color: Colors.grey), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - shippingAddress.phoneNumber == null - ? '' - : "Phone:${shippingAddress.phoneNumber}", - style: new TextStyle( - fontSize: 14.0, color: Colors.grey), - ), - ), - ], - ), - ], - ), - ), - ), - ], - ), - ], - ), - ), - Container( - padding: EdgeInsets.only(top: 20, bottom: 15, right: 15), - child: Align( - alignment: Alignment.bottomRight, - child: Container( - width: 130, - height: 40, - child: FloatingActionButton.extended( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: () async { - var address = await Navigator.push( - context, - BottomUpPageRoute(ShippingAddressList()), - ); - print('address => ${address}'); - setState(() { - if (address != null) { - this.shippingAddress = address; - } - }); - }, - icon: Icon(Icons.add), - label: Text( - 'Select \nAddress', - style: TextStyle(fontSize: 12), - ), - backgroundColor: primaryColor, - ), - ), - ), - ) - ], - ), - ); - } - - List getAddressList( - BuildContext context, List addresses) { - return addresses.asMap().entries.map((s) { - return InkWell( - onTap: () { - Navigator.push( - context, - BottomUpPageRoute(ShippingAddressEditor(shippingAddress: s.value)), - ); - }, - child: ShippingAddressRow(shippingAddress: s.value, index: s.key), - ); - }).toList(); - } - - List getAddressRows(List addresses) { - return addresses.map((s) { - return MyDataRow( - onSelectChanged: (selected) {}, - cells: [ - MyDataCell( - new Text( - s.fullName, - style: textStyle, - ), - ), - MyDataCell( - new Text( - s.phoneNumber, - style: textStyle, - ), - ), - ], - ); - }).toList(); - } -} diff --git a/lib/fcs/common/pages/package/package_editor copy.dart b/lib/fcs/common/pages/package/package_editor copy.dart new file mode 100644 index 0000000..9938f28 --- /dev/null +++ b/lib/fcs/common/pages/package/package_editor copy.dart @@ -0,0 +1,427 @@ +import 'package:fcs/fcs/common/domain/entities/market.dart'; +import 'package:fcs/fcs/common/domain/entities/package.dart'; +import 'package:fcs/fcs/common/domain/vo/shipping_address.dart'; +import 'package:fcs/fcs/common/helpers/theme.dart'; +import 'package:fcs/fcs/common/localization/app_translations.dart'; +import 'package:fcs/fcs/common/pages/market/market_editor.dart'; +import 'package:fcs/fcs/common/pages/market/model/market_model.dart'; +import 'package:fcs/fcs/common/pages/model/main_model.dart'; +import 'package:fcs/fcs/common/pages/package/tracking_id_page.dart'; +import 'package:fcs/fcs/common/pages/package/shipping_address_editor.dart'; +import 'package:fcs/fcs/common/pages/package/shipping_address_list.dart'; +import 'package:fcs/fcs/common/pages/package/shipping_address_row.dart'; +import 'package:fcs/fcs/common/pages/util.dart'; +import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; +import 'package:fcs/fcs/common/pages/widgets/display_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/fcs_expansion_tile.dart'; +import 'package:fcs/fcs/common/pages/widgets/image_slider.dart'; +import 'package:fcs/fcs/common/pages/widgets/input_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/local_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/my_data_table.dart'; +import 'package:fcs/fcs/common/pages/widgets/progress.dart'; +import 'package:fcs/pages/barcode_screen_page.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'; + +class PackageEditorPage extends StatefulWidget { + final Package package; + PackageEditorPage({this.package}); + + @override + _PackageEditorPageState createState() => _PackageEditorPageState(); +} + +class _PackageEditorPageState extends State { + TextEditingController _remarkCtl = new TextEditingController(); + TextEditingController _descCtl = new TextEditingController(); + + Package _package; + bool _isLoading = false; + List images = [ + "assets/photos/1.jpg", + "assets/photos/2.jpg", + "assets/photos/3.jpg" + ]; + ShippingAddress shippingAddress = ShippingAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'); + + @override + void initState() { + super.initState(); + _package = widget.package; + selectedMarket = _package.market; + } + + final DateFormat dateFormat = DateFormat("d MMM yyyy"); + + 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(); + } + + bool isNew = false; + + @override + Widget build(BuildContext context) { + var owner = Provider.of(context).user.hasPackages(); + + final trackingIdBox = DisplayText( + text: _package.trackingID, + labelText: getLocalString(context, "package.tracking.id"), + iconData: MaterialCommunityIcons.barcode_scan, + ); + final customerNameBox = DisplayText( + text: _package.userName, + labelText: getLocalString(context, "package.create.name"), + iconData: Icons.perm_identity, + ); + final completeProcessingBtn = fcsButton( + context, + getLocalString(context, 'package.edit.complete.process.btn'), + callack: _completeProcessing, + ); + final descBox = fcsInput(getLocalString(context, "package.edit.desc"), + MaterialCommunityIcons.message_text_outline, + controller: _descCtl, autoFocus: true); + final remarkBox = fcsInput( + getLocalString(context, "package.edit.remark"), Entypo.new_message, + controller: _remarkCtl, autoFocus: true); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(Icons.close, color: primaryColor, size: 30), + onPressed: () => Navigator.of(context).pop(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "package.edit.title", + fontSize: 20, + color: primaryColor, + ), + ), + body: ListView( + children: [ + trackingIdBox, + customerNameBox, + owner + ? isNew + ? Container() + : ExpansionTile( + title: Text( + 'Processing', + style: TextStyle( + color: primaryColor, fontWeight: FontWeight.bold), + ), + children: [ + dropDown(), + descBox, + remarkBox, + ], + ) + : Container(), + getImgSlider(images), + ExpansionTile( + 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), + ), + ], + ), + completeProcessingBtn, + SizedBox( + height: 20, + ) + ], + ), + ), + ); + } + + String selectedMarket; + Widget dropDown() { + List _markets = Provider.of(context).markets; + List markets = _markets.map((e) => e.name).toList(); + markets.insert(0, MANAGE_MARKET); + + return Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 18.0), + child: LocalText( + context, + "package.create.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, + ), + 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(), + ), + ), + ], + ); + } + + _manageMarket() { + Navigator.push( + context, + BottomUpPageRoute(MarketEditor()), + ); + } + + Widget getShippingAddressList(BuildContext context) { + return Container( + child: ExpansionTile( + title: Text( + "Shipping Addresses", + style: TextStyle( + fontWeight: FontWeight.bold, + fontStyle: FontStyle.normal, + color: primaryColor), + ), + children: [ + // Column( + // children: getAddressList(context, shipmentModel.shippingAddresses), + // ), + Container( + padding: EdgeInsets.only(left: 10, right: 10), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: new Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + children: [ + new Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + shippingAddress.fullName == null + ? '' + : shippingAddress.fullName, + style: new TextStyle( + fontSize: 15.0, + color: Colors.black, + fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + shippingAddress.addressLine1 == null + ? '' + : shippingAddress.addressLine1, + style: new TextStyle( + fontSize: 14.0, color: Colors.grey), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + shippingAddress.addressLine2 == null + ? '' + : shippingAddress.addressLine2, + style: new TextStyle( + fontSize: 14.0, color: Colors.grey), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + shippingAddress.city == null + ? '' + : shippingAddress.city, + style: new TextStyle( + fontSize: 14.0, color: Colors.grey), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + shippingAddress.state == null + ? '' + : shippingAddress.state, + style: new TextStyle( + fontSize: 14.0, color: Colors.grey), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + shippingAddress.phoneNumber == null + ? '' + : "Phone:${shippingAddress.phoneNumber}", + style: new TextStyle( + fontSize: 14.0, color: Colors.grey), + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + Container( + padding: EdgeInsets.only(top: 20, bottom: 15, right: 15), + child: Align( + alignment: Alignment.bottomRight, + child: Container( + width: 130, + height: 40, + child: FloatingActionButton.extended( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () async { + var address = await Navigator.push( + context, + BottomUpPageRoute(ShippingAddressList()), + ); + print('address => ${address}'); + setState(() { + if (address != null) { + this.shippingAddress = address; + } + }); + }, + icon: Icon(Icons.add), + label: Text( + 'Select \nAddress', + style: TextStyle(fontSize: 12), + ), + backgroundColor: primaryColor, + ), + ), + ), + ) + ], + ), + ); + } + + List getAddressList( + BuildContext context, List addresses) { + return addresses.asMap().entries.map((s) { + return InkWell( + onTap: () { + Navigator.push( + context, + BottomUpPageRoute(ShippingAddressEditor(shippingAddress: s.value)), + ); + }, + child: ShippingAddressRow(shippingAddress: s.value, index: s.key), + ); + }).toList(); + } + + List getAddressRows(List addresses) { + return addresses.map((s) { + return MyDataRow( + onSelectChanged: (selected) {}, + cells: [ + MyDataCell( + new Text( + s.fullName, + style: textStyle, + ), + ), + MyDataCell( + new Text( + s.phoneNumber, + style: textStyle, + ), + ), + ], + ); + }).toList(); + } + + _completeProcessing() {} +} diff --git a/lib/fcs/common/pages/package/package_editor.dart b/lib/fcs/common/pages/package/package_editor.dart new file mode 100644 index 0000000..a671095 --- /dev/null +++ b/lib/fcs/common/pages/package/package_editor.dart @@ -0,0 +1,271 @@ +import 'package:fcs/fcs/common/domain/entities/market.dart'; +import 'package:fcs/fcs/common/domain/entities/package.dart'; +import 'package:fcs/fcs/common/domain/vo/shipping_address.dart'; +import 'package:fcs/fcs/common/helpers/theme.dart'; +import 'package:fcs/fcs/common/pages/market/market_editor.dart'; +import 'package:fcs/fcs/common/pages/market/model/market_model.dart'; +import 'package:fcs/fcs/common/pages/package/model/package_model.dart'; +import 'package:fcs/fcs/common/pages/package/tracking_id_page.dart'; +import 'package:fcs/fcs/common/pages/util.dart'; +import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; +import 'package:fcs/fcs/common/pages/widgets/display_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/image_slider.dart'; +import 'package:fcs/fcs/common/pages/widgets/local_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/multi_img_controller.dart'; +import 'package:fcs/fcs/common/pages/widgets/multi_img_file.dart'; +import 'package:fcs/fcs/common/pages/widgets/progress.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_model.dart'; + +class PackageEditorPage extends StatefulWidget { + final Package package; + PackageEditorPage({this.package}); + + @override + _PackageEditorPageState createState() => _PackageEditorPageState(); +} + +class _PackageEditorPageState extends State { + TextEditingController _remarkCtl = new TextEditingController(); + TextEditingController _descCtl = new TextEditingController(); + + Package _package; + bool _isLoading = false; + ShippingAddress shippingAddress = ShippingAddress( + fullName: 'U Nyi Nyi', + addressLine1: '154-19 64th Ave.', + addressLine2: 'Flushing', + city: 'NY', + state: 'NY', + phoneNumber: '+1 (292)215-2247'); + + @override + void initState() { + super.initState(); + _package = widget.package; + selectedMarket = _package.market; + _descCtl.text = _package.desc; + _remarkCtl.text = _package.remark; + multiImgController.setImageUrls = _package.photoUrls; + } + + final DateFormat dateFormat = DateFormat("d MMM yyyy"); + + 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(); + } + + bool isNew = false; + MultiImgController multiImgController = MultiImgController(); + + @override + Widget build(BuildContext context) { + final trackingIdBox = DisplayText( + text: _package.trackingID, + labelText: getLocalString(context, "package.tracking.id"), + iconData: MaterialCommunityIcons.barcode_scan, + ); + final statusBox = DisplayText( + text: _package.currentStatus, + labelText: getLocalString(context, "package.edit.status"), + iconData: AntDesign.exclamationcircleo, + ); + final customerNameBox = DisplayText( + text: _package.userName, + labelText: getLocalString(context, "package.create.name"), + iconData: Icons.perm_identity, + ); + final completeProcessingBtn = fcsButton( + context, + getLocalString(context, 'package.edit.complete.process.btn'), + callack: _completeProcessing, + ); + final descBox = fcsInput(getLocalString(context, "package.edit.desc"), + MaterialCommunityIcons.message_text_outline, + controller: _descCtl, autoFocus: false); + final remarkBox = fcsInput( + getLocalString(context, "package.edit.remark"), Entypo.new_message, + controller: _remarkCtl, autoFocus: false); + final img = MultiImageFile( + enabled: true, + controller: multiImgController, + title: "Receipt File", + ); + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(Icons.close, color: primaryColor, size: 30), + onPressed: () => Navigator.of(context).pop(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "package.edit.title", + fontSize: 20, + color: primaryColor, + ), + ), + 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, + "package.edit.procseeing", + color: primaryColor, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 18.0, right: 10), + child: Column( + children: [ + marketDropdown(), + descBox, + remarkBox, + img, + ], + ), + ), + completeProcessingBtn, + SizedBox( + height: 20, + ) + ], + ), + ), + ), + ); + } + + String selectedMarket; + Widget marketDropdown() { + List _markets = Provider.of(context).markets; + List markets = _markets.map((e) => e.name).toList(); + markets.insert(0, MANAGE_MARKET); + + return Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 18.0), + child: LocalText( + context, + "package.create.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, + ), + 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(), + ), + ), + ], + ); + } + + _manageMarket() { + Navigator.push( + context, + BottomUpPageRoute(MarketEditor()), + ); + } + + _completeProcessing() async { + if (_descCtl.text == "") { + showMsgDialog(context, "Error", "Expected some description"); + return; + } + setState(() { + _isLoading = true; + }); + PackageModel packageModel = + Provider.of(context, listen: false); + try { + _package.desc = _descCtl.text; + _package.remark = _remarkCtl.text; + _package.market = selectedMarket; + await packageModel.completeProcessing(_package, + multiImgController.getAddedFile, multiImgController.getDeletedUrl); + Navigator.pop(context); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/fcs/common/pages/package/package_info.dart b/lib/fcs/common/pages/package/package_info.dart index 1388762..b4b6110 100644 --- a/lib/fcs/common/pages/package/package_info.dart +++ b/lib/fcs/common/pages/package/package_info.dart @@ -1,11 +1,20 @@ import 'package:fcs/fcs/common/domain/entities/package.dart'; import 'package:fcs/fcs/common/helpers/theme.dart'; -import 'package:fcs/fcs/common/localization/app_translations.dart'; -import 'package:fcs/fcs/common/pages/widgets/label_widgets.dart'; +import 'package:fcs/fcs/common/pages/package/package_editor.dart'; +import 'package:fcs/fcs/common/pages/util.dart'; +import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; +import 'package:fcs/fcs/common/pages/widgets/display_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/local_text.dart'; +import 'package:fcs/fcs/common/pages/widgets/multi_img_controller.dart'; +import 'package:fcs/fcs/common/pages/widgets/multi_img_file.dart'; import 'package:fcs/fcs/common/pages/widgets/progress.dart'; import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:flutter_icons/flutter_icons.dart'; import 'package:intl/intl.dart'; +import 'package:timeline_list/timeline.dart'; +import 'package:timeline_list/timeline_model.dart'; + +final DateFormat dateFormat = DateFormat("d MMM yyyy"); class PackageInfo extends StatefulWidget { final Package package; @@ -19,13 +28,13 @@ class _PackageInfoState extends State { var dateFormatter = new DateFormat('dd MMM yyyy'); Package _package; bool _isLoading = false; + MultiImgController multiImgController = MultiImgController(); @override void initState() { super.initState(); - if (widget.package != null) { - _package = widget.package; - } + _package = widget.package; + multiImgController.setImageUrls = _package.photoUrls; } @override @@ -35,17 +44,70 @@ class _PackageInfoState extends State { @override Widget build(BuildContext context) { + final trackingIdBox = DisplayText( + text: _package.trackingID, + labelText: getLocalString(context, "package.tracking.id"), + iconData: MaterialCommunityIcons.barcode_scan, + ); + final customerNameBox = DisplayText( + text: _package.userName, + labelText: getLocalString(context, "package.create.name"), + iconData: Icons.perm_identity, + ); + final statusBox = DisplayText( + text: _package.currentStatus, + labelText: getLocalString(context, "package.edit.status"), + iconData: AntDesign.exclamationcircleo, + ); + final marketBox = DisplayText( + text: _package.market ?? "-", + labelText: getLocalString(context, "package.create.market"), + iconData: Icons.store, + ); + final descBox = DisplayText( + text: _package.desc ?? "-", + labelText: getLocalString(context, "package.edit.desc"), + iconData: MaterialCommunityIcons.message_text_outline, + ); + final remarkBox = DisplayText( + text: _package.remark ?? "-", + labelText: getLocalString(context, "package.edit.remark"), + iconData: Entypo.new_message, + ); + final img = MultiImageFile( + enabled: false, + controller: multiImgController, + title: "Receipt File", + ); + return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(Icons.close), + icon: new Icon(Icons.close, color: primaryColor, size: 30), onPressed: () => Navigator.of(context).pop(), ), - backgroundColor: primaryColor, - title: Text(AppTranslations.of(context).text("package.edit.title")), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "package.info.title", + fontSize: 20, + color: primaryColor, + ), + actions: [ + IconButton( + icon: Icon(Icons.edit, color: primaryColor), + onPressed: () => Navigator.push( + context, + BottomUpPageRoute(PackageEditorPage( + package: widget.package, + )), + ), + ) + ], ), body: Card( child: Column( @@ -54,80 +116,32 @@ class _PackageInfoState extends State { child: Padding( padding: const EdgeInsets.all(10.0), child: ListView(children: [ - Container( - padding: EdgeInsets.only(top: 10), - child: Row( - children: [ - Icon( - Icons.calendar_today, - ), - Padding( - padding: const EdgeInsets.only(right: 8.0, left: 15), - child: labeledText( - context, - dateFormatter.format(_package.arrivedDate), - "package.arrival.date"), - ), - ], + 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), + ), + ], ), - Container( - padding: EdgeInsets.only(top: 10), - child: Row( - children: [ - Icon(Icons.pages), - Padding( - padding: const EdgeInsets.only(right: 8.0, left: 15), - child: labeledText(context, _package.packageNumber, - "package.number"), - ), - ], - ), - ), - Container( - padding: EdgeInsets.only(top: 10), - child: Row( - children: [ - Icon(FontAwesomeIcons.weightHanging), - Padding( - padding: const EdgeInsets.only(right: 8.0, left: 15), - child: labeledText( - context, - "${_package.weight.toString()} lb", - "package.weight"), - ), - ], - ), - ), - Container( - padding: EdgeInsets.only(top: 10), - child: Row( - children: [ - Icon(FontAwesomeIcons.tag), - Padding( - padding: const EdgeInsets.only(right: 8.0, left: 15), - child: labeledText(context, _package.rate.toString(), - "package.rate"), - ), - ], - ), - ), - Container( - padding: EdgeInsets.only(top: 10), - child: Row( - children: [ - Icon(FontAwesomeIcons.moneyBill), - Padding( - padding: const EdgeInsets.only(right: 8.0, left: 15), - child: labeledText( - context, - _package.price == null - ? "" - : "\$ " + _package.price.toString(), - "package.amount"), - ), - ], - ), + SizedBox( + height: 20, ) ]), )), @@ -137,4 +151,36 @@ class _PackageInfoState 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(); + } } diff --git a/lib/fcs/common/pages/package/package_list.dart b/lib/fcs/common/pages/package/package_list.dart index 3491dc0..964084a 100644 --- a/lib/fcs/common/pages/package/package_list.dart +++ b/lib/fcs/common/pages/package/package_list.dart @@ -1,11 +1,9 @@ import 'package:fcs/fcs/common/helpers/theme.dart'; import 'package:fcs/fcs/common/localization/app_translations.dart'; import 'package:fcs/fcs/common/pages/package/model/package_model.dart'; -import 'package:fcs/fcs/common/pages/package/package_creation.dart'; import 'package:fcs/fcs/common/pages/package/package_list_row.dart'; import 'package:fcs/fcs/common/pages/package/package_new.dart'; -import 'package:fcs/fcs/common/pages/package/search_page.dart'; -import 'package:fcs/fcs/common/pages/package/user_serach.dart'; +import 'package:fcs/fcs/common/pages/user_search/user_serach.dart'; import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; import 'package:fcs/fcs/common/pages/widgets/progress.dart'; import 'package:flutter/material.dart'; @@ -70,7 +68,7 @@ class _PackageListState extends State { ), floatingActionButton: FloatingActionButton.extended( onPressed: () { - _newPickup(); + _newPackage(); }, icon: Icon(Icons.add), label: @@ -88,13 +86,12 @@ class _PackageListState extends State { itemBuilder: (BuildContext context, int index) { return PackageListRow( package: packageModel.packages[index], - isReadOnly: false, ); })), ); } - _newPickup() { + _newPackage() { Navigator.push( context, BottomUpPageRoute(PackageNew()), diff --git a/lib/fcs/common/pages/package/package_list_row.dart b/lib/fcs/common/pages/package/package_list_row.dart index d609683..cef66aa 100644 --- a/lib/fcs/common/pages/package/package_list_row.dart +++ b/lib/fcs/common/pages/package/package_list_row.dart @@ -1,5 +1,5 @@ import 'package:fcs/fcs/common/domain/entities/package.dart'; -import 'package:fcs/fcs/common/pages/package/package_creation.dart'; +import 'package:fcs/fcs/common/pages/package/package_editor.dart'; import 'package:fcs/fcs/common/pages/package/package_info.dart'; import 'package:fcs/fcs/common/pages/util.dart'; import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; @@ -7,9 +7,8 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class PackageListRow extends StatefulWidget { - final bool isReadOnly; final Package package; - const PackageListRow({this.package, this.isReadOnly}); + const PackageListRow({this.package}); @override _PackageListRowtate createState() => _PackageListRowtate(); @@ -32,17 +31,10 @@ class _PackageListRowtate extends State { padding: EdgeInsets.only(left: 15, right: 15), child: InkWell( onTap: () { - if (widget.isReadOnly) { - Navigator.push( - context, - BottomUpPageRoute(PackageInfo(package: _package)), - ); - } else { - Navigator.push( - context, - BottomUpPageRoute(PackageCreation(package: _package)), - ); - } + Navigator.push( + context, + BottomUpPageRoute(PackageInfo(package: _package)), + ); }, child: Row( children: [ diff --git a/lib/fcs/common/pages/package/package_new.dart b/lib/fcs/common/pages/package/package_new.dart index 585a401..149e518 100644 --- a/lib/fcs/common/pages/package/package_new.dart +++ b/lib/fcs/common/pages/package/package_new.dart @@ -2,7 +2,7 @@ import 'package:fcs/fcs/common/domain/entities/package.dart'; import 'package:fcs/fcs/common/domain/entities/user.dart'; import 'package:fcs/fcs/common/helpers/theme.dart'; import 'package:fcs/fcs/common/pages/package/tracking_id_page.dart'; -import 'package:fcs/fcs/common/pages/package/user_serach.dart'; +import 'package:fcs/fcs/common/pages/user_search/user_serach.dart'; import 'package:fcs/fcs/common/pages/staff/model/staff_model.dart'; import 'package:fcs/fcs/common/pages/util.dart'; import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart'; diff --git a/lib/fcs/common/pages/package/search_page.dart b/lib/fcs/common/pages/package/search_page.dart deleted file mode 100644 index 402f94e..0000000 --- a/lib/fcs/common/pages/package/search_page.dart +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:fcs/fcs/common/domain/entities/user.dart'; -import 'package:fcs/fcs/common/pages/package/model/package_model.dart'; -import 'package:fcs/fcs/common/pages/package/user_list_row.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:fcs/fcs/common/helpers/theme.dart'; - -Future searchUser1(BuildContext context) async => await showSearch( - context: context, - delegate: UserSearchDelegate(), - ); - -class UserSearchDelegate extends SearchDelegate { - @override - ThemeData appBarTheme(BuildContext context) { - final ThemeData theme = Theme.of(context); - return theme.copyWith( - inputDecorationTheme: InputDecorationTheme( - hintStyle: TextStyle( - color: theme.primaryTextTheme.title.color, fontSize: 16)), - primaryColor: primaryColor, - primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.white), - primaryColorBrightness: Brightness.light, - primaryTextTheme: theme.textTheme, - textTheme: theme.textTheme.copyWith( - title: theme.textTheme.title.copyWith( - color: theme.primaryTextTheme.title.color, fontSize: 16)), - ); - } - - @override - List buildActions(BuildContext context) { - return [ - IconButton( - icon: Icon(Icons.clear), - onPressed: () => query = '', - ), - ]; - } - - @override - Widget buildLeading(BuildContext context) { - return IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () => close(context, null), - ); - } - - @override - Widget buildResults(BuildContext context) { - final buyerModel = Provider.of(context); - return FutureBuilder( - future: buyerModel.searchUser(query), - builder: (context, AsyncSnapshot> snapshot) { - if (snapshot.hasData) { - if (snapshot.data.length == 0) { - return Container( - child: Center( - child: Text( - "Error :No Search Buyer", - textAlign: TextAlign.center, - ), - ), - ); - } - return Container( - padding: EdgeInsets.only(top: 15), - child: ListView( - children: - snapshot.data.map((u) => UserListRow(user: u)).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( - Icons.supervised_user_circle, - size: 200, - )), - ), - ); - } -} diff --git a/lib/fcs/common/pages/signin/signup_page.dart b/lib/fcs/common/pages/signin/signup_page.dart index 4686f80..7d675c8 100644 --- a/lib/fcs/common/pages/signin/signup_page.dart +++ b/lib/fcs/common/pages/signin/signup_page.dart @@ -113,7 +113,7 @@ class _SignupPageState extends State { _isLoading = true; }); try { - await context.read().signup(nameCtl.text); + await context.read().joinInvite(nameCtl.text); Navigator.pushNamedAndRemoveUntil(context, "/home", (r) => false); } catch (e) { showMsgDialog(context, "Error", e.toString()); diff --git a/lib/fcs/common/pages/staff/model/staff_model.dart b/lib/fcs/common/pages/staff/model/staff_model.dart index 78b0250..69174c0 100644 --- a/lib/fcs/common/pages/staff/model/staff_model.dart +++ b/lib/fcs/common/pages/staff/model/staff_model.dart @@ -12,6 +12,7 @@ import 'package:logging/logging.dart'; class StaffModel extends BaseModel { final log = Logger('StaffModel'); StreamSubscription listener; + StreamSubscription privilegeListener; List employees = []; List privileges = []; @@ -25,7 +26,9 @@ class StaffModel extends BaseModel { @override logout() async { if (listener != null) listener.cancel(); + if (privilegeListener != null) privilegeListener.cancel(); employees = []; + privileges = []; } Future _loadEmployees() async { @@ -57,7 +60,7 @@ class StaffModel extends BaseModel { if (user == null || !user.hasStaffs()) return; try { - Firestore.instance + privilegeListener = Firestore.instance .collection("/$privilege_collection") .snapshots() .listen((QuerySnapshot snapshot) { @@ -68,8 +71,6 @@ class StaffModel extends BaseModel { return privilege; }).toList(); notifyListeners(); - }).onError((e) { - log.warning("Error! $e"); }); } catch (e) { log.warning("Error!! $e"); diff --git a/lib/fcs/common/pages/package/user_list_row.dart b/lib/fcs/common/pages/user_search/user_list_row.dart similarity index 97% rename from lib/fcs/common/pages/package/user_list_row.dart rename to lib/fcs/common/pages/user_search/user_list_row.dart index 7251a97..4b2dff0 100644 --- a/lib/fcs/common/pages/package/user_list_row.dart +++ b/lib/fcs/common/pages/user_search/user_list_row.dart @@ -1,6 +1,6 @@ import 'package:fcs/fcs/common/domain/entities/user.dart'; import 'package:fcs/fcs/common/helpers/theme.dart'; -import 'package:fcs/fcs/common/pages/package/user_serach.dart'; +import 'package:fcs/fcs/common/pages/user_search/user_serach.dart'; import 'package:flutter/material.dart'; class UserListRow extends StatefulWidget { diff --git a/lib/fcs/common/pages/package/user_serach.dart b/lib/fcs/common/pages/user_search/user_serach.dart similarity index 98% rename from lib/fcs/common/pages/package/user_serach.dart rename to lib/fcs/common/pages/user_search/user_serach.dart index 51b0acc..5358266 100644 --- a/lib/fcs/common/pages/package/user_serach.dart +++ b/lib/fcs/common/pages/user_search/user_serach.dart @@ -1,7 +1,7 @@ import 'package:fcs/fcs/common/domain/entities/user.dart'; import 'package:fcs/fcs/common/helpers/theme.dart'; import 'package:fcs/fcs/common/pages/package/model/package_model.dart'; -import 'package:fcs/fcs/common/pages/package/user_list_row.dart'; +import 'package:fcs/fcs/common/pages/user_search/user_list_row.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/fcs/common/pages/widgets/display_image_source.dart b/lib/fcs/common/pages/widgets/display_image_source.dart new file mode 100644 index 0000000..29f25cb --- /dev/null +++ b/lib/fcs/common/pages/widgets/display_image_source.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +import 'package:flutter/widgets.dart'; + +class DisplayImageSource { + String url; + File file; + DisplayImageSource({this.url, this.file}); + + ImageProvider get imageProvider => + file == null ? NetworkImage(url) : FileImage(file); + + @override + bool operator ==(other) { + if (identical(this, other)) { + return true; + } + return (other.file == this.file && + (other.file != null || this.file != null)) || + (other.url == this.url && (other.url != null || this.url != null)); + } + + @override + int get hashCode { + int result = 17; + result = 37 * result + file.hashCode; + return result; + } +} diff --git a/lib/fcs/common/pages/widgets/image_slider.dart b/lib/fcs/common/pages/widgets/image_slider.dart new file mode 100644 index 0000000..34eb9ff --- /dev/null +++ b/lib/fcs/common/pages/widgets/image_slider.dart @@ -0,0 +1,53 @@ +import 'package:fcs/fcs/common/helpers/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; + +getImgSlider(List images) { + return Container( + height: 130, + width: 500, + child: ListView.separated( + separatorBuilder: (context, index) => Divider( + color: Colors.black, + ), + itemCount: images.length + 1, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + if (index == images.length) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 200, + height: 70, + decoration: BoxDecoration( + border: Border.all( + color: primaryColor, + width: 2.0, + ), + ), + child: Icon(SimpleLineIcons.plus), + ), + ); + } else { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 200, + height: 70, + decoration: BoxDecoration( + border: Border.all( + color: primaryColor, + width: 2.0, + ), + ), + child: Image.network( + "https://lh3.googleusercontent.com/Fu9J7YpHnHK8QPr3kdAyEbTFyvB3h9Na69-j8CpQqWbMQP9sGplj7hVqQ5beKKLGgdyA8f5zIfqWdp2ITxuqlGkWDVuTyAtj_Rmw=w0", + width: 50, + height: 50), + ), + ); + } + }, + ), + ); +} diff --git a/lib/fcs/common/pages/widgets/multi_img_controller.dart b/lib/fcs/common/pages/widgets/multi_img_controller.dart new file mode 100644 index 0000000..5eb33e3 --- /dev/null +++ b/lib/fcs/common/pages/widgets/multi_img_controller.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +import 'display_image_source.dart'; + +typedef CallBack = void Function(); + +class MultiImgController { + List imageUrls; + List addedFiles = []; + List removedFiles = []; + + List fileContainers = []; + CallBack callback; + MultiImgController() { + fileContainers = []; + } + + set setImageUrls(List imageUrls) { + if (imageUrls == null) { + return; + } + fileContainers.clear(); + + this.imageUrls = imageUrls; + imageUrls.forEach((e) { + fileContainers.add(DisplayImageSource(url: e)); + }); + if (callback != null) { + callback(); + } + } + + void onChange(CallBack callBack) { + this.callback = callBack; + } + + set addFile(DisplayImageSource fileContainer) { + // if (fileContainers.contains(fileContainer)) return; + addedFiles.add(fileContainer); + if (callback != null) { + callback(); + } + } + + set removeFile(DisplayImageSource fileContainer) { + if (!fileContainers.contains(fileContainer)) return; + fileContainers.remove(fileContainer); + + if (addedFiles.contains(fileContainer)) { + addedFiles.remove(fileContainer); + } + if (imageUrls.contains(fileContainer.url)) { + removedFiles.add(fileContainer); + } + if (callback != null) { + callback(); + } + } + + List get getAddedFile { + return addedFiles.map((e) => e.file).toList(); + } + + List get getDeletedUrl { + return removedFiles.map((e) => e.url).toList(); + } +} diff --git a/lib/fcs/common/pages/widgets/multi_img_file.dart b/lib/fcs/common/pages/widgets/multi_img_file.dart new file mode 100644 index 0000000..9a700fc --- /dev/null +++ b/lib/fcs/common/pages/widgets/multi_img_file.dart @@ -0,0 +1,266 @@ +import 'dart:io'; + +import 'package:fcs/fcs/common/helpers/theme.dart'; +import 'package:fcs/fcs/common/pages/widgets/show_img.dart'; +import 'package:fcs/fcs/common/pages/widgets/show_multiple_img.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:image_picker/image_picker.dart'; + +import 'display_image_source.dart'; +import 'multi_img_controller.dart'; + +typedef OnFile = void Function(File); + +class MultiImageFile extends StatefulWidget { + final String title; + final bool enabled; + final ImageSource imageSource; + final MultiImgController controller; + + const MultiImageFile( + {Key key, + this.title, + this.enabled = true, + this.controller, + this.imageSource = ImageSource.gallery}) + : super(key: key); + @override + _MultiImageFileState createState() => _MultiImageFileState(); +} + +class _MultiImageFileState extends State { + List fileContainers = []; + @override + void initState() { + super.initState(); + fileContainers = widget.controller.fileContainers; + widget.controller.onChange(() { + setState(() { + this.fileContainers = widget.controller.fileContainers; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 130, + width: 500, + child: ListView.separated( + separatorBuilder: (context, index) => Divider( + color: Colors.black, + ), + itemCount: + widget.enabled ? fileContainers.length + 1 : fileContainers.length, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + if (index == fileContainers.length) { + return InkWell( + onTap: () async { + bool camera = false, gallery = false; + await _dialog( + context, () => camera = true, () => gallery = true); + if (camera || gallery) { + var selectedFile = await ImagePicker().getImage( + source: camera ? ImageSource.camera : ImageSource.gallery, + imageQuality: 80, + maxWidth: 1000); + if (selectedFile != null) { + _fileAdded(DisplayImageSource(), File(selectedFile.path)); + } + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 200, + height: 130, + decoration: BoxDecoration( + border: Border.all( + color: primaryColor, + width: 2.0, + ), + ), + child: Icon(SimpleLineIcons.plus), + ), + ), + ); + } else { + return InkWell( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ShowMultiImage( + displayImageSources: fileContainers, + )), + ), + child: Stack(alignment: Alignment.topLeft, children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 200, + height: 130, + decoration: BoxDecoration( + border: Border.all( + color: primaryColor, + width: 2.0, + ), + ), + child: fileContainers[index].file == null + ? Image.network(fileContainers[index].url, + width: 50, height: 50) + : Image.file(fileContainers[index].file, + width: 50, height: 50), + ), + ), + widget.enabled + ? Positioned( + top: 0, + right: 0, + child: Container( + height: 50, + width: 50, + child: IconButton( + icon: Icon( + Icons.close, + color: primaryColor, + ), + onPressed: () => + {_fileRemove(fileContainers[index])}), + ), + ) + : Container(), + ]), + ); + } + }, + ), + ); + } + + _fileAdded(DisplayImageSource fileContainer, File selectedFile) { + fileContainer.file = selectedFile; + setState(() { + fileContainers.add(fileContainer); + widget.controller.addFile = fileContainer; + }); + } + + _fileRemove(DisplayImageSource fileContainer) { + setState(() { + widget.controller.removeFile = fileContainer; + }); + } + + Widget getFile(DisplayImageSource fileContainer, int index) { + return Container( + height: 40, + padding: EdgeInsets.only(top: 5, bottom: 5), + child: fileContainer.file == null && fileContainer.url == null + ? IconButton( + padding: const EdgeInsets.all(3.0), + icon: Icon(Icons.attach_file), + onPressed: () async { + if (!widget.enabled) return; + bool camera = false, gallery = false; + await _dialog( + context, () => camera = true, () => gallery = true); + if (camera || gallery) { + var selectedFile = await ImagePicker().getImage( + source: camera ? ImageSource.camera : ImageSource.gallery, + imageQuality: 80, + maxWidth: 1000); + if (selectedFile != null) { + _fileAdded(fileContainer, + File.fromRawPath(await selectedFile.readAsBytes())); + } + } + }, + ) + : Padding( + padding: const EdgeInsets.only(left: 3.0), + child: InkWell( + onTap: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ShowImage( + imageFile: fileContainer.file, + url: fileContainer.file == null + ? fileContainer.url + : null, + fileName: widget.title)), + ) + }, + child: Chip( + avatar: Icon(Icons.image), + onDeleted: !widget.enabled + ? null + : () { + _fileRemove(fileContainer); + }, + deleteIcon: Icon( + Icons.close, + ), + label: Text(widget.title + " - ${index + 1}"), + ), + ), + ), + ); + } + + Future _dialog(BuildContext context, cameraPress(), photoPress()) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Container( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + FontAwesomeIcons.camera, + size: 30, + color: primaryColor, + ), + onPressed: () { + Navigator.pop(context); + cameraPress(); + }), + Text("Camera") + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.photo_library, + size: 30, + color: primaryColor, + ), + onPressed: () { + Navigator.pop(context); + photoPress(); + }), + Text("Gallery") + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/fcs/common/pages/widgets/show_img.dart b/lib/fcs/common/pages/widgets/show_img.dart new file mode 100644 index 0000000..dc955c0 --- /dev/null +++ b/lib/fcs/common/pages/widgets/show_img.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:fcs/fcs/common/helpers/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; + +class ShowImage extends StatefulWidget { + final String url; + final File imageFile; + final String fileName; + const ShowImage({Key key, this.imageFile, this.fileName, this.url}) + : super(key: key); + @override + _ShowImageState createState() => _ShowImageState(); +} + +class _ShowImageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: primaryColor, + title: Text(widget.fileName), + ), + body: Center( + child: widget.url != null || widget.imageFile != null + ? PhotoView( + imageProvider: widget.url != null + ? NetworkImage(widget.url) + : FileImage(widget.imageFile), + minScale: PhotoViewComputedScale.contained * 1) + : Container()), + ); + } +} diff --git a/lib/fcs/common/pages/widgets/show_multiple_img.dart b/lib/fcs/common/pages/widgets/show_multiple_img.dart new file mode 100644 index 0000000..ddfdaa5 --- /dev/null +++ b/lib/fcs/common/pages/widgets/show_multiple_img.dart @@ -0,0 +1,42 @@ +import 'package:fcs/fcs/common/pages/widgets/display_image_source.dart'; +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; + +class ShowMultiImage extends StatefulWidget { + final List displayImageSources; + const ShowMultiImage({Key key, this.displayImageSources}) : super(key: key); + @override + _ShowMultiImageState createState() => _ShowMultiImageState(); +} + +class _ShowMultiImageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + child: PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: (BuildContext context, int index) { + return PhotoViewGalleryPageOptions( + imageProvider: widget.displayImageSources[index].imageProvider, + initialScale: PhotoViewComputedScale.contained * 0.8, + heroAttributes: PhotoViewHeroAttributes( + tag: widget.displayImageSources[index].hashCode), + ); + }, + itemCount: widget.displayImageSources.length, + loadingBuilder: (context, event) => Center( + child: Container( + width: 20.0, + height: 20.0, + child: CircularProgressIndicator( + value: event == null + ? 0 + : event.cumulativeBytesLoaded / event.expectedTotalBytes, + ), + ), + ), + ))); + } +} diff --git a/lib/fcs/common/services/auth_imp.dart b/lib/fcs/common/services/auth_imp.dart index 4b1a35d..2fff912 100644 --- a/lib/fcs/common/services/auth_imp.dart +++ b/lib/fcs/common/services/auth_imp.dart @@ -37,6 +37,11 @@ class AuthServiceImp implements AuthService { return authFb.getUser(refreshIdToken: refreshIdToken); } + @override + Stream getUserStream(String userID) { + return authFb.user(userID); + } + @override Stream getSetting() { return authFb.settings(); @@ -47,6 +52,11 @@ class AuthServiceImp implements AuthService { return authFb.signup(userName); } + @override + Future joinInvite(String userName) { + return authFb.joinInvite(userName); + } + @override Stream onAuthStatus() { return authFb.onAuthStatus; diff --git a/lib/fcs/common/services/auth_service.dart b/lib/fcs/common/services/auth_service.dart index 216db36..1d3ddc9 100644 --- a/lib/fcs/common/services/auth_service.dart +++ b/lib/fcs/common/services/auth_service.dart @@ -8,8 +8,10 @@ abstract class AuthService { Future signout(); Future getUser({bool refreshIdToken = false}); Future signup(String userName); + Future joinInvite(String userName); Future updateProfile(String newUserName); Future hasInvite(); + Stream getUserStream(String userID); Stream getSetting(); Stream onAuthStatus(); Future getToken();