add packages

This commit is contained in:
Sai Naw Wun
2020-09-17 06:02:48 +06:30
parent 1ed6f4f00e
commit 33f0c4fd98
34 changed files with 1529 additions and 1205 deletions

View File

@@ -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<User> 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<bool> hasInvite() async {
var invited =
await requestAPI("/check_invitation", "GET", token: await getToken());
@@ -210,4 +223,17 @@ class AuthFb {
yield setting;
}
}
Stream<User> user(String userID) async* {
Stream<DocumentSnapshot> 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;
}
}
}

View File

@@ -23,7 +23,7 @@ class UserDataProvider {
}
Future<void> acceptRequest(String userID) async {
return await requestAPI("/invites", "PUT",
return await requestAPI("/accept_request", "PUT",
payload: {"id": userID}, token: await getToken());
}

View File

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

View File

@@ -10,8 +10,9 @@ class Package {
String phoneNumber;
String currentStatus;
DateTime currentStatusDate;
List<Map<String, dynamic>> allStatus;
List<String> photoUrls;
List<ShipmentStatus> shipmentHistory;
String desc;
String status;
String shipmentNumber;
@@ -39,8 +40,6 @@ class Package {
shipmentNumber + "-" + receiverNumber + " #" + boxNumber;
double get price => rate.toDouble() * weight;
List<ShipmentStatus> 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<String, dynamic> map, String docID) {
var _currentStatusDate = (map['current_status_date'] as Timestamp);
List<Map<String, dynamic>> _allStatus = List.from(map['all_status'])
.map((e) => Map<String, dynamic>.from(e))
List<ShipmentStatus> _shipmentStatus = List.from(map['all_status'])
.map((e) => ShipmentStatus.fromMap(Map<String, dynamic>.from(e)))
.toList();
List<String> _photoUrls =
map['photo_urls'] == null ? [] : map['photo_urls'].cast<List<String>>();
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<String, dynamic> toJson() =>
{'id': id, 'tracking_id': trackingID, 'market': market, 'fcs_id': fcsID};
Map<String, dynamic> toJson() => {
'id': id,
'tracking_id': trackingID,
'market': market,
'fcs_id': fcsID,
"desc": desc,
"remark": remark,
"photo_urls": photoUrls
};
}

View File

@@ -94,6 +94,7 @@ class User {
bool hasPackages() {
return hasSysAdmin() ||
hasAdmin() ||
status == userStatusJoined ||
(privileges != null ? privileges.contains('p') : false);
}

View File

@@ -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<String, dynamic> map) {
var _date = (map['date'] as Timestamp);
return ShipmentStatus(
status: map['status'],
date: _date == null ? null : _date.toDate(),
done: map['done'],
);
}
}

View File

@@ -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<String> getToken() async {
IdTokenResult token = await firebaseUser.getIdToken();
return token.token;
}
Future<String> 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;
}

View File

@@ -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<CustomerList> {
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<CustomerList> {
Widget _status(String status) {
return Text(
status == "requested" ? status : "",
(user_requested_status == status || user_invited_status == status)
? status
: "",
style: TextStyle(color: primaryColor, fontSize: 14),
);
}

View File

@@ -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<User> 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<void> joinInvite(String userName) async {
await Services.instance.authService.joinInvite(userName);
this.user =
await Services.instance.authService.getUser(refreshIdToken: true);
notifyListeners();
}
Future<void> updateProfile(String newUserName) async {
await Services.instance.authService.updateProfile(newUserName);
this.user =

View File

@@ -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<BuyerInfo> {
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<BuyerModel>(context, listen: false)
.loadBuyerProducts(buyer)
.then((b) {
if (mounted) {
setState(() {
buyer = b;
});
}
});
}
}
@override
Widget build(BuildContext context) {
var mainModel = Provider.of<MainModel>(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: <Widget>[
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: <Widget>[
dateBox,
Divider(),
accountBox,
Divider(),
Row(
children: <Widget>[
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<BuyerModel>(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<BuyerModel>(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<BuyerModel>(context).reject(buyer);
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -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<BuyerListRow> {
final double dotSize = 15.0;
Buyer _buyer = new Buyer();
@override
void initState() {
super.initState();
BuyerModel buyerModel = Provider.of<BuyerModel>(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: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: new Row(
children: <Widget>[
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: <Widget>[
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: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: getStatus(_buyer.status),
),
],
)
],
),
),
),
);
}
}

View File

@@ -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<void> completeProcessing(
Package package, List<File> files, List<String> 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());
}
}

View File

@@ -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<PackageCreation> {
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<String> _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<TimelineModel> _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: <Widget>[
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<MainModel>(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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
// Column(
// children: getAddressList(context, shipmentModel.shippingAddresses),
// ),
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
children: <Widget>[
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
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<Widget> getAddressList(
BuildContext context, List<ShippingAddress> 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<MyDataRow> getAddressRows(List<ShippingAddress> 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();
}
}

View File

@@ -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<PackageEditorPage> {
TextEditingController _remarkCtl = new TextEditingController();
TextEditingController _descCtl = new TextEditingController();
Package _package;
bool _isLoading = false;
List<String> 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<TimelineModel> _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: <Widget>[
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<MainModel>(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: <Widget>[
Container(
padding: EdgeInsets.only(left: 20),
height: 400,
child: Timeline(
children: _models(), position: TimelinePosition.Left),
),
],
),
completeProcessingBtn,
SizedBox(
height: 20,
)
],
),
),
);
}
String selectedMarket;
Widget dropDown() {
List<Market> _markets = Provider.of<MarketModel>(context).markets;
List<String> 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<String>(
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<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: value == MANAGE_MARKET
? secondaryColor
: primaryColor)),
);
}).toList(),
),
),
],
);
}
_manageMarket() {
Navigator.push<Package>(
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: <Widget>[
// Column(
// children: getAddressList(context, shipmentModel.shippingAddresses),
// ),
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
children: <Widget>[
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
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<Widget> getAddressList(
BuildContext context, List<ShippingAddress> 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<MyDataRow> getAddressRows(List<ShippingAddress> 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() {}
}

View File

@@ -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<PackageEditorPage> {
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<TimelineModel> _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: <Widget>[
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<Market> _markets = Provider.of<MarketModel>(context).markets;
List<String> 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<String>(
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<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: value == MANAGE_MARKET
? secondaryColor
: primaryColor)),
);
}).toList(),
),
),
],
);
}
_manageMarket() {
Navigator.push<Package>(
context,
BottomUpPageRoute(MarketEditor()),
);
}
_completeProcessing() async {
if (_descCtl.text == "") {
showMsgDialog(context, "Error", "Expected some description");
return;
}
setState(() {
_isLoading = true;
});
PackageModel packageModel =
Provider.of<PackageModel>(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;
});
}
}
}

View File

@@ -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<PackageInfo> {
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<PackageInfo> {
@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: <Widget>[
IconButton(
icon: Icon(Icons.edit, color: primaryColor),
onPressed: () => Navigator.push<Package>(
context,
BottomUpPageRoute(PackageEditorPage(
package: widget.package,
)),
),
)
],
),
body: Card(
child: Column(
@@ -54,80 +116,32 @@ class _PackageInfoState extends State<PackageInfo> {
child: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView(children: <Widget>[
Container(
padding: EdgeInsets.only(top: 10),
child: Row(
children: <Widget>[
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: <Widget>[
Container(
padding: EdgeInsets.only(left: 20),
height: 400,
child: Timeline(
children: _models(),
position: TimelinePosition.Left),
),
],
),
Container(
padding: EdgeInsets.only(top: 10),
child: Row(
children: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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<PackageInfo> {
),
);
}
List<TimelineModel> _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: <Widget>[
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();
}
}

View File

@@ -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<PackageList> {
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
_newPickup();
_newPackage();
},
icon: Icon(Icons.add),
label:
@@ -88,13 +86,12 @@ class _PackageListState extends State<PackageList> {
itemBuilder: (BuildContext context, int index) {
return PackageListRow(
package: packageModel.packages[index],
isReadOnly: false,
);
})),
);
}
_newPickup() {
_newPackage() {
Navigator.push(
context,
BottomUpPageRoute(PackageNew()),

View File

@@ -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<PackageListRow> {
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: <Widget>[

View File

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

View File

@@ -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<User> searchUser1(BuildContext context) async => await showSearch<User>(
context: context,
delegate: UserSearchDelegate(),
);
class UserSearchDelegate extends SearchDelegate<User> {
@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<Widget> 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<PackageModel>(context);
return FutureBuilder(
future: buyerModel.searchUser(query),
builder: (context, AsyncSnapshot<List<User>> 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<Color>(primaryColor)),
),
);
}
});
}
@override
Widget buildSuggestions(BuildContext context) {
return Container(
child: Center(
child: Opacity(
opacity: 0.2,
child: Icon(
Icons.supervised_user_circle,
size: 200,
)),
),
);
}
}

View File

@@ -113,7 +113,7 @@ class _SignupPageState extends State<SignupPage> {
_isLoading = true;
});
try {
await context.read<MainModel>().signup(nameCtl.text);
await context.read<MainModel>().joinInvite(nameCtl.text);
Navigator.pushNamedAndRemoveUntil(context, "/home", (r) => false);
} catch (e) {
showMsgDialog(context, "Error", e.toString());

View File

@@ -12,6 +12,7 @@ import 'package:logging/logging.dart';
class StaffModel extends BaseModel {
final log = Logger('StaffModel');
StreamSubscription<QuerySnapshot> listener;
StreamSubscription<QuerySnapshot> privilegeListener;
List<User> employees = [];
List<Privilege> 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<void> _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");

View File

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

View File

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

View File

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

View File

@@ -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<String> 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),
),
);
}
},
),
);
}

View File

@@ -0,0 +1,67 @@
import 'dart:io';
import 'display_image_source.dart';
typedef CallBack = void Function();
class MultiImgController {
List<String> imageUrls;
List<DisplayImageSource> addedFiles = [];
List<DisplayImageSource> removedFiles = [];
List<DisplayImageSource> fileContainers = [];
CallBack callback;
MultiImgController() {
fileContainers = [];
}
set setImageUrls(List<String> 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<File> get getAddedFile {
return addedFiles.map((e) => e.file).toList();
}
List<String> get getDeletedUrl {
return removedFiles.map((e) => e.url).toList();
}
}

View File

@@ -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<MultiImageFile> {
List<DisplayImageSource> 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: <Widget>[
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<void> _dialog(BuildContext context, cameraPress(), photoPress()) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
FontAwesomeIcons.camera,
size: 30,
color: primaryColor,
),
onPressed: () {
Navigator.pop(context);
cameraPress();
}),
Text("Camera")
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.photo_library,
size: 30,
color: primaryColor,
),
onPressed: () {
Navigator.pop(context);
photoPress();
}),
Text("Gallery")
],
),
],
),
),
),
);
},
);
}
}

View File

@@ -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<ShowImage> {
@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()),
);
}
}

View File

@@ -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<DisplayImageSource> displayImageSources;
const ShowMultiImage({Key key, this.displayImageSources}) : super(key: key);
@override
_ShowMultiImageState createState() => _ShowMultiImageState();
}
class _ShowMultiImageState extends State<ShowMultiImage> {
@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,
),
),
),
)));
}
}

View File

@@ -37,6 +37,11 @@ class AuthServiceImp implements AuthService {
return authFb.getUser(refreshIdToken: refreshIdToken);
}
@override
Stream<User> getUserStream(String userID) {
return authFb.user(userID);
}
@override
Stream<Setting> getSetting() {
return authFb.settings();
@@ -47,6 +52,11 @@ class AuthServiceImp implements AuthService {
return authFb.signup(userName);
}
@override
Future<User> joinInvite(String userName) {
return authFb.joinInvite(userName);
}
@override
Stream<User> onAuthStatus() {
return authFb.onAuthStatus;

View File

@@ -8,8 +8,10 @@ abstract class AuthService {
Future<void> signout();
Future<User> getUser({bool refreshIdToken = false});
Future<User> signup(String userName);
Future<User> joinInvite(String userName);
Future<void> updateProfile(String newUserName);
Future<bool> hasInvite();
Stream<User> getUserStream(String userID);
Stream<Setting> getSetting();
Stream<User> onAuthStatus();
Future<String> getToken();