add barcode scan
This commit is contained in:
@@ -92,6 +92,8 @@ flutter {
|
|||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
@@ -100,6 +102,7 @@ dependencies {
|
|||||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
||||||
implementation 'com.google.firebase:firebase-auth:19.0.0'
|
implementation 'com.google.firebase:firebase-auth:19.0.0'
|
||||||
implementation 'com.google.firebase:firebase-messaging:20.1.0'
|
implementation 'com.google.firebase:firebase-messaging:20.1.0'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.61'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
@@ -7,6 +8,8 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||||
classpath 'com.google.gms:google-services:4.3.3'
|
classpath 'com.google.gms:google-services:4.3.3'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' //add this line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
"invite.name.enter":"Please enter your name",
|
"invite.name.enter":"Please enter your name",
|
||||||
"invite.request":"Request Invitation",
|
"invite.request":"Request Invitation",
|
||||||
"invite.request.successful":"Successfully requested!",
|
"invite.request.successful":"Successfully requested!",
|
||||||
|
"invite.btn":"Invite",
|
||||||
|
|
||||||
"customer.list.title":"Customers",
|
"customer.list.title":"Customers",
|
||||||
"customer.name":"Name",
|
"customer.name":"Name",
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"staff.form.title":"Staff",
|
"staff.form.title":"Staff",
|
||||||
"staff.add":"Add",
|
"staff.add":"Add",
|
||||||
"staff.update":"Update",
|
"staff.update":"Update",
|
||||||
|
"staff.phone.search":"Enter phone number",
|
||||||
|
|
||||||
"profile.title": "My Profile",
|
"profile.title": "My Profile",
|
||||||
"profile.edit_title": "Edit My Profile",
|
"profile.edit_title": "Edit My Profile",
|
||||||
@@ -93,6 +95,22 @@
|
|||||||
"profile.email":"Email",
|
"profile.email":"Email",
|
||||||
"profile.privilege":"Privilege",
|
"profile.privilege":"Privilege",
|
||||||
|
|
||||||
|
"package.btn.name":"Track Packages",
|
||||||
|
"package.title":"Packages",
|
||||||
|
"package.create.title":"New Packages",
|
||||||
|
"package.create.fcs.id":"FCS ID",
|
||||||
|
"package.create.name":"Customer Name",
|
||||||
|
"package.create.phone":"Phone Number",
|
||||||
|
"package.tracking.id":"Tracking ID",
|
||||||
|
"package.create.packages":"Create Packages",
|
||||||
|
|
||||||
|
"package.edit.title":"PACKAGE",
|
||||||
|
"package.arrival.date":"Arrival Date",
|
||||||
|
"package.number":"Box Number",
|
||||||
|
"package.rate":"Rate",
|
||||||
|
"package.weight":"Weight",
|
||||||
|
"package.amount":"Amount",
|
||||||
|
|
||||||
|
|
||||||
"btn.save": "Save",
|
"btn.save": "Save",
|
||||||
"btn.approve":"Approve",
|
"btn.approve":"Approve",
|
||||||
@@ -595,17 +613,6 @@
|
|||||||
"box.width":"Width",
|
"box.width":"Width",
|
||||||
"box.height":"Height",
|
"box.height":"Height",
|
||||||
|
|
||||||
"package.name":"Packages",
|
|
||||||
"package.title":"PACKAGES",
|
|
||||||
"package.new":"New Package",
|
|
||||||
"package.edit.title":"PACKAGE",
|
|
||||||
"package.arrival.date":"Arrival Date",
|
|
||||||
"package.number":"Box Number",
|
|
||||||
"package.rate":"Rate",
|
|
||||||
"package.weight":"Weight",
|
|
||||||
"package.amount":"Amount",
|
|
||||||
|
|
||||||
|
|
||||||
"pickup.date": "Pickup Date",
|
"pickup.date": "Pickup Date",
|
||||||
"pickup.location_time": "Pickup Location / Time",
|
"pickup.location_time": "Pickup Location / Time",
|
||||||
"pickup.information": "Pickup Informations",
|
"pickup.information": "Pickup Informations",
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
"faq.btn":"မေးလေ့ရှိသောမေးခွန်းများ",
|
"faq.btn":"မေးလေ့ရှိသောမေးခွန်းများ",
|
||||||
"faq.title":"မေးလေ့ရှိသောမေးခွန်းများ",
|
"faq.title":"မေးလေ့ရှိသောမေးခွန်းများ",
|
||||||
|
|
||||||
"faq.add.title":"မေးလေ့ရှိသောမေးခွန်း အသစ်",
|
"faq.add.title":"မေးခွန်း အသစ်",
|
||||||
"faq.edit.title":"မေးလေ့ရှိသောမေးခွန်း ပြုပြင်ခြင်း",
|
"faq.edit.title":"မေးခွန်း ပြုပြင်ခြင်း",
|
||||||
"faq.edit.eng":"အင်္ဂလိပ်",
|
"faq.edit.eng":"အင်္ဂလိပ်",
|
||||||
"faq.edit.mm":"မြန်မာ",
|
"faq.edit.mm":"မြန်မာ",
|
||||||
"faq.edit.question":"မေးခွန်း",
|
"faq.edit.question":"မေးခွန်း",
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
"invite.name.enter":"နာမည် ရိုက်ထည့်ပါ",
|
"invite.name.enter":"နာမည် ရိုက်ထည့်ပါ",
|
||||||
"invite.request":"ဖိတ်ကြားမှု တောင်းဆိုမည်",
|
"invite.request":"ဖိတ်ကြားမှု တောင်းဆိုမည်",
|
||||||
"invite.request.successful":"တောင်းဆိုမှု အောင်မြင်သည်!",
|
"invite.request.successful":"တောင်းဆိုမှု အောင်မြင်သည်!",
|
||||||
|
"invite.btn":"ဖိတ်ကြားမည်",
|
||||||
|
|
||||||
"customer.list.title":"ဝယ်ယူသူများ",
|
"customer.list.title":"ဝယ်ယူသူများ",
|
||||||
"customer.name":"နာမည်",
|
"customer.name":"နာမည်",
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"staff.form.title":"ဝန်ထမ်း",
|
"staff.form.title":"ဝန်ထမ်း",
|
||||||
"staff.add":"အသစ်ထည့်မည်",
|
"staff.add":"အသစ်ထည့်မည်",
|
||||||
"staff.update":"ပြုပြင်မည်",
|
"staff.update":"ပြုပြင်မည်",
|
||||||
|
"staff.phone.search":"ဖုန်းနံပါတ် ရိုက်ထည့်ပါ",
|
||||||
|
|
||||||
"profile.title":"ကျွန်ုပ် ပရိုဖိုင်",
|
"profile.title":"ကျွန်ုပ် ပရိုဖိုင်",
|
||||||
"profile.edit_title":"ကျွန်ုပ် ပရိုဖိုင်ကိုပြုပြင်ရန်",
|
"profile.edit_title":"ကျွန်ုပ် ပရိုဖိုင်ကိုပြုပြင်ရန်",
|
||||||
@@ -93,6 +95,25 @@
|
|||||||
"profile.email":"အီးမေးလ်",
|
"profile.email":"အီးမေးလ်",
|
||||||
"profile.privilege":"လုပ်ပိုင်ခွင့်",
|
"profile.privilege":"လုပ်ပိုင်ခွင့်",
|
||||||
|
|
||||||
|
|
||||||
|
"package.btn.name":"အထုပ်များ",
|
||||||
|
"package.title":"အထုပ်များ",
|
||||||
|
"package.create.title":"အထုပ် အသစ်များ",
|
||||||
|
"package.create.fcs.id":"FCS ID",
|
||||||
|
"package.create.name":"နာမည်",
|
||||||
|
"package.create.phone":"ဖုန်းနံပါတ်",
|
||||||
|
"package.tracking.id":"Tracking ID",
|
||||||
|
"package.create.packages":"အထုပ် အသစ်များ သိမ်းဆည်းရန်",
|
||||||
|
|
||||||
|
"package.new":"New Package",
|
||||||
|
"package.edit.title":"PACKAGE",
|
||||||
|
"package.arrival.date":"Arrival Date",
|
||||||
|
"package.number":"Package Number",
|
||||||
|
"package.rate":"Rate",
|
||||||
|
"package.weight":"Weight",
|
||||||
|
"package.amount":"Amount",
|
||||||
|
|
||||||
|
|
||||||
"btn.save":"သိမ်းဆည်းရန်",
|
"btn.save":"သိမ်းဆည်းရန်",
|
||||||
"btn.approve":"အတည်ပြုရန်",
|
"btn.approve":"အတည်ပြုရန်",
|
||||||
"btn.delete":"ဖျက်ရန်",
|
"btn.delete":"ဖျက်ရန်",
|
||||||
@@ -590,16 +611,6 @@
|
|||||||
"shipment.form.title":"FCS SHIPMENT",
|
"shipment.form.title":"FCS SHIPMENT",
|
||||||
"shipment.number":"FCS Shipment Number",
|
"shipment.number":"FCS Shipment Number",
|
||||||
|
|
||||||
"package.name":"Packages",
|
|
||||||
"package.title":"PACKAGES",
|
|
||||||
"package.new":"New Package",
|
|
||||||
"package.edit.title":"PACKAGE",
|
|
||||||
"package.arrival.date":"Arrival Date",
|
|
||||||
"package.number":"Package Number",
|
|
||||||
"package.rate":"Rate",
|
|
||||||
"package.weight":"Weight",
|
|
||||||
"package.amount":"Amount",
|
|
||||||
|
|
||||||
"pickup": "Shipments",
|
"pickup": "Shipments",
|
||||||
"pickup.title": "SHIPMENTS",
|
"pickup.title": "SHIPMENTS",
|
||||||
"pickup.new": "New Shipment",
|
"pickup.new": "New Shipment",
|
||||||
|
|||||||
17
lib/app.dart
17
lib/app.dart
@@ -8,6 +8,8 @@ import 'package:fcs/fcs/common/pages/faq/model/faq_model.dart';
|
|||||||
import 'package:fcs/fcs/common/pages/initial_language_selection.dart';
|
import 'package:fcs/fcs/common/pages/initial_language_selection.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/main_model.dart' as fcs;
|
import 'package:fcs/fcs/common/pages/model/main_model.dart' as fcs;
|
||||||
|
import 'package:fcs/fcs/common/pages/package/model/package_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/model/shipment_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/staff/model/staff_model.dart';
|
import 'package:fcs/fcs/common/pages/staff/model/staff_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/term/model/term_model.dart';
|
import 'package:fcs/fcs/common/pages/term/model/term_model.dart';
|
||||||
import 'package:fcs/fcs/common/services/services.dart';
|
import 'package:fcs/fcs/common/services/services.dart';
|
||||||
@@ -19,11 +21,9 @@ import 'package:fcs/model/notification_model.dart';
|
|||||||
import 'package:fcs/model/pd_model.dart';
|
import 'package:fcs/model/pd_model.dart';
|
||||||
import 'package:fcs/model/reg_model.dart';
|
import 'package:fcs/model/reg_model.dart';
|
||||||
import 'package:fcs/model/report_model.dart';
|
import 'package:fcs/model/report_model.dart';
|
||||||
import 'package:fcs/model/shipment_model.dart';
|
|
||||||
import 'package:fcs/model/storage_model.dart';
|
import 'package:fcs/model/storage_model.dart';
|
||||||
import 'package:fcs/model/test_model.dart';
|
import 'package:fcs/model/test_model.dart';
|
||||||
import 'package:fcs/model_fcs/message_model.dart';
|
import 'package:fcs/model_fcs/message_model.dart';
|
||||||
import 'package:fcs/model_fcs/package_model.dart';
|
|
||||||
import 'package:fcs/pages/email_page.dart';
|
import 'package:fcs/pages/email_page.dart';
|
||||||
import 'package:fcs/pages/login_page.dart';
|
import 'package:fcs/pages/login_page.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
@@ -85,14 +85,14 @@ class _AppState extends State<App> {
|
|||||||
final ReportUserModel reportUserModel = new ReportUserModel();
|
final ReportUserModel reportUserModel = new ReportUserModel();
|
||||||
final PickUpModel pickUpModel = new PickUpModel();
|
final PickUpModel pickUpModel = new PickUpModel();
|
||||||
final ShipmentRateModel shipmentRateModel = new ShipmentRateModel();
|
final ShipmentRateModel shipmentRateModel = new ShipmentRateModel();
|
||||||
final ShipmentModel shipmentModel = new ShipmentModel();
|
|
||||||
final PackageModel packageModel = new PackageModel();
|
|
||||||
final BoxModel boxModel = new BoxModel();
|
final BoxModel boxModel = new BoxModel();
|
||||||
final MessageModel messageModel = new MessageModel();
|
final MessageModel messageModel = new MessageModel();
|
||||||
final InvoiceModel invoiceModel = new InvoiceModel();
|
final InvoiceModel invoiceModel = new InvoiceModel();
|
||||||
final CustomerModel customerModel = new CustomerModel();
|
final CustomerModel customerModel = new CustomerModel();
|
||||||
final DiscountModel discountModel = new DiscountModel();
|
final DiscountModel discountModel = new DiscountModel();
|
||||||
final StaffModel staffModel = new StaffModel();
|
final StaffModel staffModel = new StaffModel();
|
||||||
|
final ShipmentModel shipmentModel = new ShipmentModel();
|
||||||
|
final PackageModel packageModel = new PackageModel();
|
||||||
|
|
||||||
AppTranslationsDelegate _newLocaleDelegate;
|
AppTranslationsDelegate _newLocaleDelegate;
|
||||||
static FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
static FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||||
@@ -101,8 +101,11 @@ class _AppState extends State<App> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mainModel2..addModel(customerModel);
|
mainModel2
|
||||||
mainModel2..addModel(staffModel);
|
..addModel(customerModel)
|
||||||
|
..addModel(staffModel)
|
||||||
|
..addModel(shipmentModel)
|
||||||
|
..addModel(packageModel);
|
||||||
mainModel2.init();
|
mainModel2.init();
|
||||||
|
|
||||||
_newLocaleDelegate = AppTranslationsDelegate(newLocale: null);
|
_newLocaleDelegate = AppTranslationsDelegate(newLocale: null);
|
||||||
@@ -129,8 +132,6 @@ class _AppState extends State<App> {
|
|||||||
..addModel(reportUserModel)
|
..addModel(reportUserModel)
|
||||||
..addModel(pickUpModel)
|
..addModel(pickUpModel)
|
||||||
..addModel(shipmentRateModel)
|
..addModel(shipmentRateModel)
|
||||||
..addModel(shipmentModel)
|
|
||||||
..addModel(packageModel)
|
|
||||||
..addModel(boxModel)
|
..addModel(boxModel)
|
||||||
..addModel(messageModel)
|
..addModel(messageModel)
|
||||||
..addModel(shipmentRateModel)
|
..addModel(shipmentRateModel)
|
||||||
|
|||||||
16
lib/fcs/common/data/providers/package_data_provider.dart
Normal file
16
lib/fcs/common/data/providers/package_data_provider.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/entities/package.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/api_helper.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/firebase_helper.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
class PackageDataProvider {
|
||||||
|
final log = Logger('PackageDataProvider');
|
||||||
|
|
||||||
|
Future<void> createPackages(User user, List<Package> packages) async {
|
||||||
|
List<String> strs = packages.map((e) => e.trackingID).toList();
|
||||||
|
return await requestAPI("/packages", "POST",
|
||||||
|
payload: {"fcs_id": user.fcsID, "tracking_ids": strs},
|
||||||
|
token: await getToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:fcs/config.dart';
|
||||||
import 'package:fcs/fcs/common/domain/constants.dart';
|
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/api_helper.dart';
|
import 'package:fcs/fcs/common/helpers/api_helper.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/firebase_helper.dart';
|
import 'package:fcs/fcs/common/helpers/firebase_helper.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class UserDataProvider {
|
class UserDataProvider {
|
||||||
|
final log = Logger('UserDataProvider');
|
||||||
|
|
||||||
Future<void> inviteUser(String userName, String phoneNumber) async {
|
Future<void> inviteUser(String userName, String phoneNumber) async {
|
||||||
return await requestAPI("/invites", "POST",
|
return await requestAPI("/invites", "POST",
|
||||||
payload: {"user_name": userName, "phone_number": phoneNumber},
|
payload: {"user_name": userName, "phone_number": phoneNumber},
|
||||||
@@ -35,4 +41,33 @@ class UserDataProvider {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<User>> searchUser(String term) async {
|
||||||
|
if (term == null || term == '') return List();
|
||||||
|
|
||||||
|
var bytes = utf8.encode(term);
|
||||||
|
var base64Str = base64.encode(bytes);
|
||||||
|
HtmlEscape htmlEscape = const HtmlEscape();
|
||||||
|
String escapeBuyer = htmlEscape.convert(base64Str);
|
||||||
|
|
||||||
|
int limit = 20;
|
||||||
|
List<User> users = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = await requestAPI(
|
||||||
|
"/api/fts/$user_collection/$escapeBuyer/$limit", "GET",
|
||||||
|
url: Config.instance.reportURL, token: await getToken());
|
||||||
|
|
||||||
|
if (data == null) return List();
|
||||||
|
|
||||||
|
data.forEach((buyer) {
|
||||||
|
var user = User.fromJson(buyer);
|
||||||
|
users.add(user);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.warning("buyer error:" + e.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
|
||||||
|
|
||||||
class UserLocalDataProvider {
|
|
||||||
Future<User> getUser(String id) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
61
lib/fcs/common/domain/entities/package.dart
Normal file
61
lib/fcs/common/domain/entities/package.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/vo/shipment_status.dart';
|
||||||
|
|
||||||
|
class Package {
|
||||||
|
String id;
|
||||||
|
String trackingID;
|
||||||
|
String userID;
|
||||||
|
String userName;
|
||||||
|
String phoneNumber;
|
||||||
|
String status;
|
||||||
|
|
||||||
|
String shipmentNumber;
|
||||||
|
String senderFCSID;
|
||||||
|
String senderName;
|
||||||
|
String receiverFCSID;
|
||||||
|
String receiverName;
|
||||||
|
String receiverAddress;
|
||||||
|
String receiverNumber;
|
||||||
|
String boxNumber;
|
||||||
|
String cargoDesc;
|
||||||
|
String market;
|
||||||
|
|
||||||
|
int rate;
|
||||||
|
int weight;
|
||||||
|
String packageType;
|
||||||
|
String pickUpID;
|
||||||
|
List<String> photos;
|
||||||
|
String remark;
|
||||||
|
DateTime arrivedDate;
|
||||||
|
|
||||||
|
int get amount => rate != null && weight != null ? rate * weight : 0;
|
||||||
|
|
||||||
|
String get packageNumber =>
|
||||||
|
shipmentNumber + "-" + receiverNumber + " #" + boxNumber;
|
||||||
|
double get price => rate.toDouble() * weight;
|
||||||
|
|
||||||
|
List<ShipmentStatus> shipmentHistory;
|
||||||
|
|
||||||
|
Package({
|
||||||
|
this.id,
|
||||||
|
this.trackingID,
|
||||||
|
this.userID,
|
||||||
|
this.shipmentNumber,
|
||||||
|
this.senderFCSID,
|
||||||
|
this.senderName,
|
||||||
|
this.receiverFCSID,
|
||||||
|
this.receiverName,
|
||||||
|
this.receiverNumber,
|
||||||
|
this.receiverAddress,
|
||||||
|
this.boxNumber,
|
||||||
|
this.rate,
|
||||||
|
this.weight,
|
||||||
|
this.packageType,
|
||||||
|
this.pickUpID,
|
||||||
|
this.remark,
|
||||||
|
this.status,
|
||||||
|
this.arrivedDate,
|
||||||
|
this.cargoDesc,
|
||||||
|
this.market,
|
||||||
|
this.shipmentHistory,
|
||||||
|
});
|
||||||
|
}
|
||||||
25
lib/fcs/common/domain/entities/shipment.dart
Normal file
25
lib/fcs/common/domain/entities/shipment.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
class Shipment {
|
||||||
|
DateTime shipDate;
|
||||||
|
String shipmentNumber;
|
||||||
|
DateTime cutoffDate;
|
||||||
|
String shipType;
|
||||||
|
DateTime arrivalDate;
|
||||||
|
DateTime departureDate;
|
||||||
|
String consignee;
|
||||||
|
String port;
|
||||||
|
String destination;
|
||||||
|
String status;
|
||||||
|
String remark;
|
||||||
|
Shipment(
|
||||||
|
{this.shipDate,
|
||||||
|
this.shipmentNumber,
|
||||||
|
this.cutoffDate,
|
||||||
|
this.shipType,
|
||||||
|
this.status,
|
||||||
|
this.arrivalDate,
|
||||||
|
this.departureDate,
|
||||||
|
this.consignee,
|
||||||
|
this.port,
|
||||||
|
this.destination,
|
||||||
|
this.remark});
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ class User {
|
|||||||
return User(
|
return User(
|
||||||
id: json['id'],
|
id: json['id'],
|
||||||
name: json['user_name'],
|
name: json['user_name'],
|
||||||
|
fcsID: json['fcs_id'],
|
||||||
phoneNumber: json['phone_number'],
|
phoneNumber: json['phone_number'],
|
||||||
status: json['status'],
|
status: json['status'],
|
||||||
);
|
);
|
||||||
@@ -90,6 +91,12 @@ class User {
|
|||||||
(privileges != null ? privileges.contains('sp') : false);
|
(privileges != null ? privileges.contains('sp') : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasPackages() {
|
||||||
|
return hasSysAdmin() ||
|
||||||
|
hasAdmin() ||
|
||||||
|
(privileges != null ? privileges.contains('p') : false);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'User{name: $name, phoneNumber: $phoneNumber,status:$status}';
|
return 'User{name: $name, phoneNumber: $phoneNumber,status:$status}';
|
||||||
|
|||||||
6
lib/fcs/common/domain/vo/shipment_status.dart
Normal file
6
lib/fcs/common/domain/vo/shipment_status.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class ShipmentStatus {
|
||||||
|
String status;
|
||||||
|
DateTime date;
|
||||||
|
bool done;
|
||||||
|
ShipmentStatus({this.status, this.date, this.done});
|
||||||
|
}
|
||||||
15
lib/fcs/common/domain/vo/shipping_address.dart
Normal file
15
lib/fcs/common/domain/vo/shipping_address.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
class ShippingAddress {
|
||||||
|
String fullName;
|
||||||
|
String addressLine1;
|
||||||
|
String addressLine2;
|
||||||
|
String city;
|
||||||
|
String state;
|
||||||
|
String phoneNumber;
|
||||||
|
ShippingAddress(
|
||||||
|
{this.fullName,
|
||||||
|
this.addressLine1,
|
||||||
|
this.addressLine2,
|
||||||
|
this.city,
|
||||||
|
this.state,
|
||||||
|
this.phoneNumber});
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import 'package:fcs/fcs/common/domain/entities/setting.dart';
|
|
||||||
import 'package:fcs/fcs/common/domain/vo/contact.dart';
|
import 'package:fcs/fcs/common/domain/vo/contact.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/theme.dart';
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
||||||
import 'package:fcs/fcs/common/pages/contact/model/contact_model.dart';
|
import 'package:fcs/fcs/common/pages/contact/model/contact_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
|
||||||
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
|
||||||
import 'package:fcs/fcs/common/pages/util.dart';
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/input_text.dart';
|
import 'package:fcs/fcs/common/pages/widgets/input_text.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
||||||
@@ -73,6 +70,9 @@ class _ContactEditorState extends State<ContactEditor> {
|
|||||||
iconData: FontAwesomeIcons.facebook,
|
iconData: FontAwesomeIcons.facebook,
|
||||||
controller: _facebook);
|
controller: _facebook);
|
||||||
|
|
||||||
|
final saveBox = fcsButton(context, getLocalString(context, "btn.save"),
|
||||||
|
callack: _submit);
|
||||||
|
|
||||||
return LocalProgress(
|
return LocalProgress(
|
||||||
inAsyncCall: _isLoading,
|
inAsyncCall: _isLoading,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@@ -98,19 +98,11 @@ class _ContactEditorState extends State<ContactEditor> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => _submit(),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.save,
|
|
||||||
color: Colors.white,
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate([
|
delegate: SliverChildListDelegate([
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left:18.0,right:18),
|
padding: const EdgeInsets.only(left: 18.0, right: 18),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
itemTitle(context, "contact.callus"),
|
itemTitle(context, "contact.callus"),
|
||||||
@@ -125,7 +117,14 @@ class _ContactEditorState extends State<ContactEditor> {
|
|||||||
emailBox,
|
emailBox,
|
||||||
Divider(),
|
Divider(),
|
||||||
itemTitle(context, "contact.visitus"),
|
itemTitle(context, "contact.visitus"),
|
||||||
faceBookBox
|
faceBookBox,
|
||||||
|
SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
saveBox,
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class _ContactPageState extends State<ContactPage> {
|
|||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
expandedHeight: 150.0,
|
expandedHeight: 100.0,
|
||||||
floating: false,
|
floating: false,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
|||||||
@@ -67,29 +67,21 @@ class _CustomerEditorState extends State<CustomerEditor> {
|
|||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 18.0, right: 18),
|
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: <Widget>[
|
||||||
phoneNumberBox,
|
phoneNumberBox,
|
||||||
DisplayText(
|
DisplayText(
|
||||||
text: widget.customer.status,
|
text: widget.customer.status,
|
||||||
labelText:
|
labelText: getLocalString(context, "customer.status"),
|
||||||
getLocalString(context, "customer.status"),
|
|
||||||
iconData: Icons.add_alarm,
|
iconData: Icons.add_alarm,
|
||||||
),
|
),
|
||||||
DisplayText(
|
DisplayText(
|
||||||
text: widget.customer.fcsID,
|
text: widget.customer.fcsID,
|
||||||
labelText:
|
labelText: getLocalString(context, "customer.fcs.id"),
|
||||||
getLocalString(context, "customer.fcs.id"),
|
|
||||||
iconData: Icons.account_circle,
|
iconData: Icons.account_circle,
|
||||||
),
|
),
|
||||||
],
|
SizedBox(
|
||||||
),
|
height: 20,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
widget.customer.requested
|
widget.customer.requested
|
||||||
? fcsButton(
|
? fcsButton(
|
||||||
|
|||||||
152
lib/fcs/common/pages/customers/invitation_create.dart
Normal file
152
lib/fcs/common/pages/customers/invitation_create.dart
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import 'package:country_code_picker/country_code_picker.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/customers/model/customer_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
|
import 'package:fcs/widget/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class InvitationCreate extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_InvitationCreateState createState() => _InvitationCreateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InvitationCreateState extends State<InvitationCreate> {
|
||||||
|
TextEditingController _nameController = new TextEditingController();
|
||||||
|
TextEditingController _phoneController = new TextEditingController();
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
String dialCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
dialCode = "+95";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
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("invitation.new")),
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
padding: EdgeInsets.all(18),
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
fcsInput(getLocalString(context, "customer.name"), Icons.person,
|
||||||
|
controller: _nameController, autoFocus: true),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.phone,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey[400], width: 1),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12.0))),
|
||||||
|
child: CountryCodePicker(
|
||||||
|
onChanged: _countryChange,
|
||||||
|
initialSelection: dialCode,
|
||||||
|
countryFilter: ['+95', '+1'],
|
||||||
|
showCountryOnly: false,
|
||||||
|
showOnlyCountryWhenClosed: false,
|
||||||
|
alignLeft: false,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(top: 10, bottom: 10),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _phoneController,
|
||||||
|
cursorColor: primaryColor,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
fillColor: Colors.white,
|
||||||
|
labelText: getLocalString(context, "customer.phone"),
|
||||||
|
labelStyle:
|
||||||
|
TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
|
filled: true,
|
||||||
|
focusedBorder: UnderlineInputBorder(
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: Colors.grey, width: 1.0)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
fcsButton(context, getLocalString(context, "invite.btn"),
|
||||||
|
callack: _invite),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_countryChange(CountryCode countryCode) {
|
||||||
|
setState(() {
|
||||||
|
dialCode = countryCode.dialCode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_invite() async {
|
||||||
|
String userName = _nameController.text;
|
||||||
|
String phoneNumber = dialCode + _phoneController.text;
|
||||||
|
if (userName == null ||
|
||||||
|
userName == "" ||
|
||||||
|
phoneNumber == null ||
|
||||||
|
phoneNumber == "") {
|
||||||
|
showMsgDialog(context, "Error", "Invalid name or phone number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
CustomerModel customerModel =
|
||||||
|
Provider.of<CustomerModel>(context, listen: false);
|
||||||
|
await customerModel.inviteUser(userName, phoneNumber);
|
||||||
|
Navigator.pop(context);
|
||||||
|
} catch (e) {
|
||||||
|
showMsgDialog(context, "Error", e.toString());
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
import 'package:country_code_picker/country_code_picker.dart';
|
|
||||||
import 'package:fcs/fcs/common/helpers/theme.dart';
|
|
||||||
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
|
||||||
import 'package:fcs/fcs/common/pages/customers/model/customer_model.dart';
|
|
||||||
import 'package:fcs/fcs/common/pages/util.dart';
|
|
||||||
import 'package:fcs/widget/progress.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class InvitationDetail extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
_InvitationDetailState createState() => _InvitationDetailState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InvitationDetailState extends State<InvitationDetail> {
|
|
||||||
TextEditingController _nameController = new TextEditingController();
|
|
||||||
TextEditingController _phoneController = new TextEditingController();
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
|
||||||
String dialCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
dialCode = "+95";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
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("invitation.new")),
|
|
||||||
),
|
|
||||||
body: Container(
|
|
||||||
padding: EdgeInsets.all(18),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
children: <Widget>[
|
|
||||||
fcsInput("Name", Icons.person,
|
|
||||||
controller: _nameController, autoFocus: true),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Icon(
|
|
||||||
Icons.phone,
|
|
||||||
color: primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border:
|
|
||||||
Border.all(color: Colors.grey[400], width: 1),
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.all(Radius.circular(12.0))),
|
|
||||||
child: CountryCodePicker(
|
|
||||||
onChanged: _countryChange,
|
|
||||||
initialSelection: dialCode,
|
|
||||||
countryFilter: ['+95', '+1'],
|
|
||||||
showCountryOnly: false,
|
|
||||||
showOnlyCountryWhenClosed: false,
|
|
||||||
alignLeft: false,
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(top: 10, bottom: 10),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _phoneController,
|
|
||||||
cursorColor: primaryColor,
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
keyboardType: TextInputType.phone,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
fillColor: Colors.white,
|
|
||||||
labelText: "Phone number",
|
|
||||||
labelStyle:
|
|
||||||
TextStyle(fontSize: 16, color: Colors.grey),
|
|
||||||
filled: true,
|
|
||||||
focusedBorder: UnderlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Colors.grey, width: 1.0)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
fcsButton(context, "Invite", callack: _invite),
|
|
||||||
SizedBox(height: 10)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_countryChange(CountryCode countryCode) {
|
|
||||||
setState(() {
|
|
||||||
dialCode = countryCode.dialCode;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_invite() async {
|
|
||||||
String userName = _nameController.text;
|
|
||||||
String phoneNumber = dialCode + _phoneController.text;
|
|
||||||
if (userName == null ||
|
|
||||||
userName == "" ||
|
|
||||||
phoneNumber == null ||
|
|
||||||
phoneNumber == "") {
|
|
||||||
showMsgDialog(context, "Error", "Invalid name or phone number");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
CustomerModel customerModel =
|
|
||||||
Provider.of<CustomerModel>(context, listen: false);
|
|
||||||
await customerModel.inviteUser(userName, phoneNumber);
|
|
||||||
Navigator.pop(context);
|
|
||||||
} catch (e) {
|
|
||||||
showMsgDialog(context, "Error", e.toString());
|
|
||||||
} finally {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
|||||||
import 'package:share/share.dart';
|
import 'package:share/share.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
||||||
import 'package:fcs/fcs/common/pages/customers/invitation_detail.dart';
|
import 'package:fcs/fcs/common/pages/customers/invitation_create.dart';
|
||||||
import 'package:fcs/fcs/common/pages/customers/model/customer_model.dart';
|
import 'package:fcs/fcs/common/pages/customers/model/customer_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/util.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/bottom_up_page_route.dart';
|
||||||
@@ -51,10 +51,10 @@ class _InvitationListState extends State<InvitationList> {
|
|||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).push(BottomUpPageRoute(InvitationDetail()));
|
Navigator.of(context).push(BottomUpPageRoute(InvitationCreate()));
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.add),
|
icon: Icon(Icons.add),
|
||||||
label: Text(AppTranslations.of(context).text("invitation.new")),
|
label: LocalText(context, "invitation.new", color: Colors.white),
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
),
|
),
|
||||||
body: new ListView.separated(
|
body: new ListView.separated(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:fcs/fcs/common/domain/constants.dart';
|
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
@@ -10,6 +12,8 @@ class CustomerModel extends BaseModel {
|
|||||||
|
|
||||||
List<User> customers = [];
|
List<User> customers = [];
|
||||||
List<User> invitations = [];
|
List<User> invitations = [];
|
||||||
|
StreamSubscription<QuerySnapshot> customerListener;
|
||||||
|
StreamSubscription<QuerySnapshot> invitationListener;
|
||||||
|
|
||||||
void initUser(user) async {
|
void initUser(user) async {
|
||||||
super.initUser(user);
|
super.initUser(user);
|
||||||
@@ -38,7 +42,9 @@ class CustomerModel extends BaseModel {
|
|||||||
if (user == null || !user.hasCustomers()) return;
|
if (user == null || !user.hasCustomers()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Firestore.instance
|
if (customerListener != null) customerListener.cancel();
|
||||||
|
|
||||||
|
customerListener = Firestore.instance
|
||||||
.collection("/$user_collection")
|
.collection("/$user_collection")
|
||||||
.where("is_sys_admin", isEqualTo: false)
|
.where("is_sys_admin", isEqualTo: false)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
@@ -50,11 +56,9 @@ class CustomerModel extends BaseModel {
|
|||||||
return user;
|
return user;
|
||||||
}).toList();
|
}).toList();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}).onError((e) {
|
|
||||||
log.warning("Error! $e");
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warning("Error!! $e");
|
log.warning("error:$e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +66,9 @@ class CustomerModel extends BaseModel {
|
|||||||
if (user == null || !user.hasCustomers()) return;
|
if (user == null || !user.hasCustomers()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Firestore.instance
|
if (invitationListener != null) invitationListener.cancel();
|
||||||
|
|
||||||
|
invitationListener = Firestore.instance
|
||||||
.collection("/$invitations_collection")
|
.collection("/$invitations_collection")
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.listen((QuerySnapshot snapshot) {
|
.listen((QuerySnapshot snapshot) {
|
||||||
@@ -73,21 +79,9 @@ class CustomerModel extends BaseModel {
|
|||||||
return user;
|
return user;
|
||||||
}).toList();
|
}).toList();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}).onError((e) {
|
|
||||||
log.warning("Error! $e");
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warning("Error!! $e");
|
log.warning("error:$e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> updatePrivileges(String userID, List<String> privileges) async {
|
|
||||||
// try {
|
|
||||||
// await request("/employee/privileges", "PUT",
|
|
||||||
// payload: {"id": userID, "privileges": privileges},
|
|
||||||
// token: await getToken());
|
|
||||||
// } catch (e) {
|
|
||||||
// throw Exception(e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,17 @@ class FAQDetailPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _FAQDetailPageState extends State<FAQDetailPage> {
|
class _FAQDetailPageState extends State<FAQDetailPage> {
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
FAQ faq;
|
||||||
|
|
||||||
|
intState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
faq = context.select((FAQModel m) => m.getFAQ(widget.faq.id));
|
||||||
|
if (faq == null) return Text("Deleted");
|
||||||
|
|
||||||
bool isEditable = context.select((MainModel m) => m.faqEditable());
|
bool isEditable = context.select((MainModel m) => m.faqEditable());
|
||||||
return LocalProgress(
|
return LocalProgress(
|
||||||
inAsyncCall: _isLoading,
|
inAsyncCall: _isLoading,
|
||||||
@@ -62,7 +70,7 @@ class _FAQDetailPageState extends State<FAQDetailPage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context).push<void>(CupertinoPageRoute(
|
Navigator.of(context).push<void>(CupertinoPageRoute(
|
||||||
builder: (context) => FAQEditor(faq: widget.faq),
|
builder: (context) => FAQEditor(faq: faq),
|
||||||
)),
|
)),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
CupertinoIcons.pen,
|
CupertinoIcons.pen,
|
||||||
@@ -78,11 +86,11 @@ class _FAQDetailPageState extends State<FAQDetailPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
getQuestion(context, widget.faq),
|
getQuestion(context, faq),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
),
|
),
|
||||||
getAnwser(context, widget.faq)
|
getAnwser(context, faq)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -118,7 +126,7 @@ class _FAQDetailPageState extends State<FAQDetailPage> {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
FAQModel faqModel = Provider.of<FAQModel>(context, listen: false);
|
FAQModel faqModel = Provider.of<FAQModel>(context, listen: false);
|
||||||
await faqModel.deleteFAQ(widget.faq);
|
await faqModel.deleteFAQ(faq);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showMsgDialog(context, "Error", e.toString());
|
showMsgDialog(context, "Error", e.toString());
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ class _FAQEditorState extends State<FAQEditor> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final snBox = InputText(
|
final snBox = InputText(
|
||||||
controller: _sn,
|
controller: _sn,
|
||||||
|
labelTextKey: "faq.edit.sn",
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
withBorder: true,
|
withBorder: false,
|
||||||
textInputType: TextInputType.number,
|
textInputType: TextInputType.number,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -97,14 +98,6 @@ class _FAQEditorState extends State<FAQEditor> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => _save(),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.save,
|
|
||||||
color: Colors.white,
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate([
|
delegate: SliverChildListDelegate([
|
||||||
@@ -114,15 +107,7 @@ class _FAQEditorState extends State<FAQEditor> {
|
|||||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
snBox,
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 18.0, left: 0),
|
|
||||||
child: subItemTitle(context, "faq.edit.sn"),
|
|
||||||
),
|
|
||||||
Expanded(child: snBox),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Center(child: itemTitle(context, "faq.edit.eng")),
|
Center(child: itemTitle(context, "faq.edit.eng")),
|
||||||
subItemTitle(context, "faq.edit.question",
|
subItemTitle(context, "faq.edit.question",
|
||||||
iconData: SimpleLineIcons.question),
|
iconData: SimpleLineIcons.question),
|
||||||
@@ -138,6 +123,11 @@ class _FAQEditorState extends State<FAQEditor> {
|
|||||||
subItemTitle(context, "faq.edit.answer",
|
subItemTitle(context, "faq.edit.answer",
|
||||||
iconData: MaterialCommunityIcons.message_reply_text),
|
iconData: MaterialCommunityIcons.message_reply_text),
|
||||||
answerMmBox,
|
answerMmBox,
|
||||||
|
fcsButton(context, getLocalString(context, "btn.save"),
|
||||||
|
callack: _save),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import 'package:fcs/fcs/common/domain/entities/faq.dart';
|
import 'package:fcs/fcs/common/domain/entities/faq.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/theme.dart';
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
||||||
import 'package:fcs/fcs/common/pages/faq/faq_detail_page.dart';
|
|
||||||
import 'package:fcs/fcs/common/pages/faq/faq_edit_page.dart';
|
import 'package:fcs/fcs/common/pages/faq/faq_edit_page.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
import 'package:fcs/fcs/common/pages/model/main_model.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/local_text.dart';
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -12,15 +13,25 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'model/faq_model.dart';
|
import 'model/faq_model.dart';
|
||||||
|
|
||||||
|
const Duration _kExpand = Duration(milliseconds: 200);
|
||||||
|
|
||||||
class FAQListPage extends StatefulWidget {
|
class FAQListPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_FAQListPageState createState() => _FAQListPageState();
|
_FAQListPageState createState() => _FAQListPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FAQListPageState extends State<FAQListPage> {
|
class _FAQListPageState extends State<FAQListPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
AnimationController _controller;
|
||||||
|
Animation<double> _iconTurns;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_controller = AnimationController(duration: _kExpand, vsync: this);
|
||||||
|
var _halfTween = Tween<double>(begin: 0.0, end: 0.5);
|
||||||
|
var _easeInTween = CurveTween(curve: Curves.easeIn);
|
||||||
|
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -29,6 +40,16 @@ class _FAQListPageState extends State<FAQListPage> {
|
|||||||
bool isEditable = context.select((MainModel m) => m.faqEditable());
|
bool isEditable = context.select((MainModel m) => m.faqEditable());
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
floatingActionButton: isEditable
|
||||||
|
? FloatingActionButton.extended(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(BottomUpPageRoute(FAQEditor()));
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
label: LocalText(context, "faq.add.title", color: Colors.white),
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
@@ -45,7 +66,8 @@ class _FAQListPageState extends State<FAQListPage> {
|
|||||||
pinned: true,
|
pinned: true,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
titlePadding: EdgeInsets.symmetric(vertical: 10),
|
titlePadding:
|
||||||
|
EdgeInsets.symmetric(vertical: 10, horizontal: 45),
|
||||||
title: Text(AppTranslations.of(context).text('faq.title'),
|
title: Text(AppTranslations.of(context).text('faq.title'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -54,21 +76,21 @@ class _FAQListPageState extends State<FAQListPage> {
|
|||||||
actions: isEditable
|
actions: isEditable
|
||||||
? [
|
? [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () =>
|
onPressed: () => setState(() {
|
||||||
Navigator.of(context).push<void>(CupertinoPageRoute(
|
isEditMode = !isEditMode;
|
||||||
builder: (context) => FAQEditor(),
|
}),
|
||||||
)),
|
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
CupertinoIcons.add,
|
Icons.edit,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 35,
|
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
),
|
),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, index) => _faqItem(context, faqModel.faqs[index]),
|
(context, index) {
|
||||||
|
return _faqItem(context, faqModel.faqs[index]);
|
||||||
|
},
|
||||||
childCount: faqModel.faqs.length,
|
childCount: faqModel.faqs.length,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -76,44 +98,44 @@ class _FAQListPageState extends State<FAQListPage> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isEditMode = false;
|
||||||
|
|
||||||
Widget _faqItem(BuildContext context, FAQ faq) {
|
Widget _faqItem(BuildContext context, FAQ faq) {
|
||||||
bool isEng = Provider.of<LanguageModel>(context).isEng;
|
bool isEng = Provider.of<LanguageModel>(context).isEng;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
Padding(
|
||||||
onTap: () {
|
padding: const EdgeInsets.all(8.0),
|
||||||
Navigator.of(context).push(CupertinoPageRoute(
|
child: FcsExpansionTile(
|
||||||
builder: (context) => FAQDetailPage(
|
isEdit: isEditMode,
|
||||||
faq: faq,
|
title: TextLocalStyle(
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(15),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: TextLocalStyle(
|
|
||||||
context,
|
context,
|
||||||
faq.question(isEng),
|
faq.question(isEng),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
|
onEditPress: () {
|
||||||
|
Navigator.of(context).push<void>(CupertinoPageRoute(
|
||||||
|
builder: (context) => FAQEditor(faq: faq),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
children: [getAnwser(context, faq)],
|
||||||
),
|
),
|
||||||
// Spacer(),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 18.0),
|
|
||||||
child: Icon(
|
|
||||||
CupertinoIcons.right_chevron,
|
|
||||||
color: primaryColor,
|
|
||||||
size: 22,
|
|
||||||
),
|
),
|
||||||
)
|
Divider(
|
||||||
],
|
thickness: 2,
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
Divider(),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getAnwser(BuildContext context, FAQ faq) {
|
||||||
|
bool isEng = Provider.of<LanguageModel>(context).isEng;
|
||||||
|
return TextLocalStyle(
|
||||||
|
context,
|
||||||
|
faq.answer(isEng),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w200,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,43 +11,47 @@ class FAQModel extends BaseModel {
|
|||||||
|
|
||||||
List<FAQ> faqs = [];
|
List<FAQ> faqs = [];
|
||||||
|
|
||||||
|
FAQ getFAQ(String id) {
|
||||||
|
return faqs.firstWhere((e) => e.id == id, orElse: () => null);
|
||||||
|
}
|
||||||
|
|
||||||
StreamSubscription<QuerySnapshot> listener;
|
StreamSubscription<QuerySnapshot> listener;
|
||||||
|
|
||||||
FAQModel() {
|
FAQModel() {
|
||||||
Stream<QuerySnapshot> query = Firestore.instance
|
if (listener != null) listener.cancel();
|
||||||
|
try {
|
||||||
|
listener = Firestore.instance
|
||||||
.collection("/faqs")
|
.collection("/faqs")
|
||||||
.orderBy("sn", descending: false)
|
.orderBy("sn", descending: false)
|
||||||
.snapshots();
|
.snapshots()
|
||||||
if (listener != null) {
|
.listen((snaps) {
|
||||||
listener.cancel();
|
|
||||||
}
|
|
||||||
listener = query.listen((snaps) {
|
|
||||||
faqs.clear();
|
faqs.clear();
|
||||||
snaps.documents.forEach((d) {
|
snaps.documents.forEach((d) {
|
||||||
faqs.add(FAQ.fromMap(d.data, d.documentID));
|
faqs.add(FAQ.fromMap(d.data, d.documentID));
|
||||||
});
|
});
|
||||||
|
print("in listener");
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.warning("error:$e");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addFAQ(FAQ faq) async {
|
Future<void> addFAQ(FAQ faq) async {
|
||||||
await request("/faqs", "POST",
|
await request("/faqs", "POST",
|
||||||
payload: faq.toMap(),
|
payload: faq.toMap(),
|
||||||
token: await Services.instance.authService.getToken());
|
token: await Services.instance.authService.getToken());
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateFAQ(FAQ faq) async {
|
Future<void> updateFAQ(FAQ faq) async {
|
||||||
await request("/faqs", "PUT",
|
await request("/faqs", "PUT",
|
||||||
payload: faq.toMap(),
|
payload: faq.toMap(),
|
||||||
token: await Services.instance.authService.getToken());
|
token: await Services.instance.authService.getToken());
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteFAQ(FAQ faq) async {
|
Future<void> deleteFAQ(FAQ faq) async {
|
||||||
await request("/faqs", "DELETE",
|
await request("/faqs", "DELETE",
|
||||||
payload: faq.toMap(),
|
payload: faq.toMap(),
|
||||||
token: await Services.instance.authService.getToken());
|
token: await Services.instance.authService.getToken());
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:fcs/fcs/common/pages/customers/customer_list.dart';
|
|||||||
import 'package:fcs/fcs/common/pages/faq/faq_list_page.dart';
|
import 'package:fcs/fcs/common/pages/faq/faq_list_page.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/package_list.dart';
|
||||||
import 'package:fcs/fcs/common/pages/staff/staff_list.dart';
|
import 'package:fcs/fcs/common/pages/staff/staff_list.dart';
|
||||||
import 'package:fcs/fcs/common/pages/util.dart';
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/action_button.dart';
|
import 'package:fcs/fcs/common/pages/widgets/action_button.dart';
|
||||||
@@ -15,7 +16,6 @@ import 'package:fcs/pages/shipment_list.dart';
|
|||||||
import 'package:fcs/pages/term.dart';
|
import 'package:fcs/pages/term.dart';
|
||||||
import 'package:fcs/pages_fcs/box_list.dart';
|
import 'package:fcs/pages_fcs/box_list.dart';
|
||||||
import 'package:fcs/pages_fcs/delivery_list.dart';
|
import 'package:fcs/pages_fcs/delivery_list.dart';
|
||||||
import 'package:fcs/pages_fcs/package_list.dart';
|
|
||||||
import 'package:fcs/widget/banner.dart';
|
import 'package:fcs/widget/banner.dart';
|
||||||
import 'package:fcs/widget/bottom_up_page_route.dart';
|
import 'package:fcs/widget/bottom_up_page_route.dart';
|
||||||
import 'package:fcs/widget/offline_redirect.dart';
|
import 'package:fcs/widget/offline_redirect.dart';
|
||||||
@@ -84,7 +84,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
builder: (context) => FAQListPage(),
|
builder: (context) => FAQListPage(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
final packagesBtn = _buildBtn("package.name",
|
final packagesBtn = _buildBtn("package.btn.name",
|
||||||
icon: Octicons.package,
|
icon: Octicons.package,
|
||||||
btnCallback: () =>
|
btnCallback: () =>
|
||||||
Navigator.of(context).push(BottomUpPageRoute(PackageList())));
|
Navigator.of(context).push(BottomUpPageRoute(PackageList())));
|
||||||
@@ -180,7 +180,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
user.hasStaffs() ? widgets.add(staffBtn) : "";
|
user.hasStaffs() ? widgets.add(staffBtn) : "";
|
||||||
// owner ? widgets.add(fcsProfileBtn) : "";
|
// owner ? widgets.add(fcsProfileBtn) : "";
|
||||||
// widgets.add(shipmentCostBtn);
|
// widgets.add(shipmentCostBtn);
|
||||||
// customer || owner ? widgets.add(packagesBtn) : "";
|
user.hasPackages() ? widgets.add(packagesBtn) : "";
|
||||||
// owner ? widgets.add(boxesBtn) : "";
|
// owner ? widgets.add(boxesBtn) : "";
|
||||||
// owner ? widgets.add(deliveryBtn) : "";
|
// owner ? widgets.add(deliveryBtn) : "";
|
||||||
user.hasCustomers() ? widgets.add(customersBtn) : "";
|
user.hasCustomers() ? widgets.add(customersBtn) : "";
|
||||||
@@ -253,7 +253,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.push(BottomUpPageRoute(SigninPage()));
|
.push(BottomUpPageRoute(SigninPage()));
|
||||||
},
|
},
|
||||||
// iconSize: 30,
|
|
||||||
child: Text(
|
child: Text(
|
||||||
"Sign In",
|
"Sign In",
|
||||||
style: siginButtonStyle,
|
style: siginButtonStyle,
|
||||||
@@ -262,40 +261,14 @@ class _HomePageState extends State<HomePage> {
|
|||||||
]),
|
]),
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient:
|
gradient: LinearGradient(
|
||||||
// RadialGradient(
|
|
||||||
// center: const Alignment(-0.7, 0.6), // near the top right
|
|
||||||
// radius: 0.6,
|
|
||||||
// colors: [
|
|
||||||
// secondaryColor,
|
|
||||||
// primaryColor, // yellow sun
|
|
||||||
// ],
|
|
||||||
// stops: [0.4, 1.0],
|
|
||||||
// )
|
|
||||||
LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment
|
end: Alignment.bottomCenter,
|
||||||
.bottomCenter, // 10% of the width, so there are ten blinds.
|
|
||||||
colors: [
|
colors: [
|
||||||
Color(0xd0272262),
|
Color(0xd0272262),
|
||||||
Color(0xfa272262),
|
Color(0xfa272262),
|
||||||
// Color(0xa0ff4400),
|
],
|
||||||
// secondaryColor,
|
|
||||||
], // whitish to gray
|
|
||||||
),
|
),
|
||||||
// SweepGradient(
|
|
||||||
// center: FractionalOffset.centerLeft,
|
|
||||||
// startAngle: 0.0,
|
|
||||||
// endAngle: math.pi * 2,
|
|
||||||
// colors: const <Color>[
|
|
||||||
// secondaryColor,
|
|
||||||
// primaryColor,
|
|
||||||
// secondaryColor,
|
|
||||||
// primaryColor,
|
|
||||||
// secondaryColor,
|
|
||||||
// ],
|
|
||||||
// stops: const <double>[0.0, 0.25, 0.5, 0.75, 1.0],
|
|
||||||
// ),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|||||||
69
lib/fcs/common/pages/package/barcode_screen_page.dart
Normal file
69
lib/fcs/common/pages/package/barcode_screen_page.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:barcode_scan/barcode_scan.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/vo/buyer.dart';
|
||||||
|
import 'package:fcs/widget/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_icons/flutter_icons.dart';
|
||||||
|
|
||||||
|
class BarcodeScreenPage extends StatefulWidget {
|
||||||
|
final BuyerProduct buyerProduct;
|
||||||
|
const BarcodeScreenPage({Key key, this.buyerProduct}) : super(key: key);
|
||||||
|
@override
|
||||||
|
_BarcodeScreenPageState createState() => _BarcodeScreenPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BarcodeScreenPageState extends State<BarcodeScreenPage> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
bool _isLoading = false;
|
||||||
|
String scanResult;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LocalProgress(
|
||||||
|
inAsyncCall: _isLoading,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
title: Text("Bar Code Scranner"),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
InkWell(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"Scan : ",
|
||||||
|
style: TextStyle(
|
||||||
|
color: primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Ionicons.ios_qr_scanner,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
await scan();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future scan() async {
|
||||||
|
var result = await BarcodeScanner.scan();
|
||||||
|
print("ScanResult => $result");
|
||||||
|
setState(() => scanResult = result);
|
||||||
|
}
|
||||||
|
}
|
||||||
279
lib/fcs/common/pages/package/buyer_info.dart
Normal file
279
lib/fcs/common/pages/package/buyer_info.dart
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
lib/fcs/common/pages/package/buyer_list_row.dart
Normal file
105
lib/fcs/common/pages/package/buyer_list_row.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
196
lib/fcs/common/pages/package/model/package_model.dart
Normal file
196
lib/fcs/common/pages/package/model/package_model.dart
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/entities/package.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/shipment_status.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/model/base_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/services/services.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
class PackageModel extends BaseModel {
|
||||||
|
final log = Logger('PackageModel');
|
||||||
|
|
||||||
|
StreamSubscription<QuerySnapshot> listener;
|
||||||
|
static List<ShipmentStatus> statusHistory = [
|
||||||
|
ShipmentStatus(status: "Received", date: DateTime(2020, 6, 1), done: true),
|
||||||
|
ShipmentStatus(status: "Processed", date: DateTime(2020, 6, 1), done: true),
|
||||||
|
ShipmentStatus(status: "Shipped", date: DateTime(2020, 6, 5), done: false),
|
||||||
|
ShipmentStatus(status: "Arrived", date: DateTime(2020, 6, 7), done: false),
|
||||||
|
ShipmentStatus(
|
||||||
|
status: "Delivered", date: DateTime(2020, 6, 15), done: false)
|
||||||
|
];
|
||||||
|
|
||||||
|
static List<Package> packages = [
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A202",
|
||||||
|
receiverNumber: "3",
|
||||||
|
receiverName: "Ko Myo Min",
|
||||||
|
boxNumber: "1",
|
||||||
|
rate: 7,
|
||||||
|
packageType: "General",
|
||||||
|
weight: 25,
|
||||||
|
status: "Received",
|
||||||
|
receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon',
|
||||||
|
cargoDesc: "Computers",
|
||||||
|
arrivedDate: DateTime(2020, 6, 1),
|
||||||
|
market: "Amazon",
|
||||||
|
id: "PKG2039",
|
||||||
|
trackingID: "23-234s-asdfl",
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A202",
|
||||||
|
receiverNumber: "3",
|
||||||
|
receiverName: "Ko Myo Min",
|
||||||
|
boxNumber: "2",
|
||||||
|
rate: 7,
|
||||||
|
packageType: "General",
|
||||||
|
weight: 20,
|
||||||
|
status: "Received",
|
||||||
|
cargoDesc: "Clothes",
|
||||||
|
arrivedDate: DateTime(2020, 6, 1),
|
||||||
|
market: "Macy",
|
||||||
|
trackingID: "asd-sdf-23498",
|
||||||
|
id: "PKG2040",
|
||||||
|
receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon',
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A202",
|
||||||
|
receiverNumber: "3",
|
||||||
|
receiverName: "Ko Myo Min",
|
||||||
|
boxNumber: "3",
|
||||||
|
rate: 7,
|
||||||
|
packageType: "General",
|
||||||
|
weight: 15,
|
||||||
|
cargoDesc: "Shoes",
|
||||||
|
status: "Processed",
|
||||||
|
market: "Macy",
|
||||||
|
trackingID: "8923-234-sd",
|
||||||
|
id: "PKG2041",
|
||||||
|
arrivedDate: DateTime(2020, 6, 1),
|
||||||
|
receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon',
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A202",
|
||||||
|
receiverNumber: "2",
|
||||||
|
receiverName: "Ma Aye",
|
||||||
|
boxNumber: "1",
|
||||||
|
rate: 8,
|
||||||
|
packageType: "Medicine",
|
||||||
|
weight: 15,
|
||||||
|
status: "Processed",
|
||||||
|
market: "Macy",
|
||||||
|
trackingID: "lsdf-sd09sdf",
|
||||||
|
cargoDesc: "Dietary supplement",
|
||||||
|
id: "PKG2042",
|
||||||
|
arrivedDate: DateTime(2020, 6, 1),
|
||||||
|
receiverAddress: '2 Shwe Taung Kyar St, Bahan Tsp, Yangon',
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A202",
|
||||||
|
receiverNumber: "2",
|
||||||
|
receiverName: "Ma Aye",
|
||||||
|
boxNumber: "2",
|
||||||
|
rate: 7,
|
||||||
|
packageType: "General",
|
||||||
|
cargoDesc: "Handbags",
|
||||||
|
weight: 55,
|
||||||
|
market: "Macy",
|
||||||
|
trackingID: "234-sdflsdf-213",
|
||||||
|
status: "Shipped",
|
||||||
|
id: "PKG2043",
|
||||||
|
arrivedDate: DateTime(2020, 6, 1),
|
||||||
|
receiverAddress: '2 Shwe Taung Kyar St, Bahan Tsp, Yangon',
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A201",
|
||||||
|
receiverNumber: "1",
|
||||||
|
receiverName: "Ko Wai",
|
||||||
|
boxNumber: "1",
|
||||||
|
rate: 9,
|
||||||
|
packageType: "Dangerous",
|
||||||
|
cargoDesc: "Phones and Scooters",
|
||||||
|
weight: 25,
|
||||||
|
status: "Arrived",
|
||||||
|
market: "Amazon",
|
||||||
|
trackingID: "sdf-asdf-23489",
|
||||||
|
id: "PKG2044",
|
||||||
|
arrivedDate: DateTime(2020, 5, 21),
|
||||||
|
receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon',
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
Package(
|
||||||
|
shipmentNumber: "A201",
|
||||||
|
receiverNumber: "1",
|
||||||
|
receiverName: "Ko Wai",
|
||||||
|
boxNumber: "2",
|
||||||
|
rate: 7,
|
||||||
|
packageType: "General",
|
||||||
|
cargoDesc: "Construction tools",
|
||||||
|
weight: 5,
|
||||||
|
status: "Processed",
|
||||||
|
market: "Amazon",
|
||||||
|
id: "PKG2045",
|
||||||
|
trackingID: "oiuw-sdfpo-234",
|
||||||
|
arrivedDate: DateTime(2020, 5, 21),
|
||||||
|
receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon',
|
||||||
|
shipmentHistory: statusHistory),
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Package> get getPackages {
|
||||||
|
return packages
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e2.packageNumber.compareTo(e1.packageNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Package> get completed {
|
||||||
|
return packages.where((e) => e.status == "Processed").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e2.packageNumber.compareTo(e1.packageNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Package> get shipped {
|
||||||
|
return packages.where((e) => e.status == "Shipped").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e2.packageNumber.compareTo(e1.packageNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Package> get arrived {
|
||||||
|
return packages.where((e) => e.status == "Arrived").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e2.packageNumber.compareTo(e1.packageNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Package> get delivered {
|
||||||
|
return packages.where((e) => e.status == "Delivered").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e2.packageNumber.compareTo(e1.packageNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Package> get upcoming {
|
||||||
|
return packages.where((e) => e.status == "Received").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e2.packageNumber.compareTo(e1.packageNumber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void initUser(user) {
|
||||||
|
super.initUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
logout() async {
|
||||||
|
if (listener != null) await listener.cancel();
|
||||||
|
packages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<User>> searchUser(String term) {
|
||||||
|
return Services.instance.userService.searchUser(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPackages(User user, List<Package> packages) {}
|
||||||
|
}
|
||||||
102
lib/fcs/common/pages/package/model/shipment_model.dart
Normal file
102
lib/fcs/common/pages/package/model/shipment_model.dart
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/entities/shipment.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/shipping_address.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/model/base_model.dart';
|
||||||
|
|
||||||
|
class ShipmentModel extends BaseModel {
|
||||||
|
List<String> shipmentType = ['Air', 'Ship', 'Cargo Truck'];
|
||||||
|
List<Shipment> shipments = [
|
||||||
|
Shipment(
|
||||||
|
shipDate: DateTime(2020, 4, 23),
|
||||||
|
shipmentNumber: 'A103B',
|
||||||
|
status: 'In Progress',
|
||||||
|
arrivalDate: DateTime(2020, 4, 30),
|
||||||
|
departureDate: DateTime(2020, 4, 23)),
|
||||||
|
Shipment(
|
||||||
|
shipDate: DateTime(2020, 4, 2),
|
||||||
|
shipmentNumber: 'A100A',
|
||||||
|
status: 'Ready to ship',
|
||||||
|
arrivalDate: DateTime(2020, 4, 28),
|
||||||
|
departureDate: DateTime(2020, 4, 15)),
|
||||||
|
Shipment(
|
||||||
|
shipDate: DateTime(2020, 4, 2),
|
||||||
|
shipmentNumber: 'A100B',
|
||||||
|
status: 'Arrived',
|
||||||
|
arrivalDate: DateTime(2020, 4, 28),
|
||||||
|
departureDate: DateTime(2020, 4, 15)),
|
||||||
|
Shipment(
|
||||||
|
shipDate: DateTime(2020, 4, 10),
|
||||||
|
shipmentNumber: 'A102B',
|
||||||
|
status: 'Canceled',
|
||||||
|
arrivalDate: DateTime(2020, 4, 20),
|
||||||
|
departureDate: DateTime(2020, 4, 10)),
|
||||||
|
Shipment(
|
||||||
|
shipDate: DateTime(2020, 4, 2),
|
||||||
|
shipmentNumber: 'A100B',
|
||||||
|
status: 'Canceled',
|
||||||
|
arrivalDate: DateTime(2020, 4, 20),
|
||||||
|
departureDate: DateTime(2020, 4, 23)),
|
||||||
|
Shipment(
|
||||||
|
shipDate: DateTime(2020, 4, 10),
|
||||||
|
shipmentNumber: 'A102B',
|
||||||
|
status: 'Arrived',
|
||||||
|
arrivalDate: DateTime(2020, 4, 30),
|
||||||
|
departureDate: DateTime(2020, 4, 20),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Shipment> get canceled {
|
||||||
|
List<Shipment> _p = shipments.where((e) => e.status == "Canceled").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e1.shipDate.compareTo(e2.shipDate);
|
||||||
|
});
|
||||||
|
return _p;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Shipment> get completed {
|
||||||
|
return shipments.where((e) => e.status == "Arrived").toList()
|
||||||
|
..sort((e1, e2) {
|
||||||
|
return e1.shipDate.compareTo(e2.shipDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Shipment> get upcoming {
|
||||||
|
List<Shipment> _shipments = shipments
|
||||||
|
.where((e) =>
|
||||||
|
e.status == "In Progress" ||
|
||||||
|
e.status == "Ready to ship" ||
|
||||||
|
e.status == "Processed" ||
|
||||||
|
e.status == "Rescheduled")
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
_shipments.sort((e1, e2) {
|
||||||
|
return e1.shipDate.compareTo(e2.shipDate);
|
||||||
|
});
|
||||||
|
return _shipments;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ShippingAddress> shippingAddresses = [
|
||||||
|
ShippingAddress(
|
||||||
|
fullName: 'U Nyi Nyi',
|
||||||
|
addressLine1: '154-19 64th Ave.',
|
||||||
|
addressLine2: 'Flushing',
|
||||||
|
city: 'NY',
|
||||||
|
state: 'NY',
|
||||||
|
phoneNumber: '+1 (292)215-2247'),
|
||||||
|
ShippingAddress(
|
||||||
|
fullName: 'Mg Myo',
|
||||||
|
addressLine1: '153-154 5th Thitsar.',
|
||||||
|
addressLine2: 'South Okkalapa Township',
|
||||||
|
city: 'Yangon',
|
||||||
|
state: 'Myanmar',
|
||||||
|
phoneNumber: '+09 95724 8750'),
|
||||||
|
];
|
||||||
|
|
||||||
|
void initUser(user) {
|
||||||
|
super.initUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
logout() async {
|
||||||
|
shipments = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
549
lib/fcs/common/pages/package/package_creation.dart
Normal file
549
lib/fcs/common/pages/package/package_creation.dart
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
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/barcode_screen_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: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();
|
||||||
|
}
|
||||||
|
}
|
||||||
140
lib/fcs/common/pages/package/package_info.dart
Normal file
140
lib/fcs/common/pages/package/package_info.dart
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
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/widgets/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class PackageInfo extends StatefulWidget {
|
||||||
|
final Package package;
|
||||||
|
PackageInfo({this.package});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PackageInfoState createState() => _PackageInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PackageInfoState extends State<PackageInfo> {
|
||||||
|
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||||
|
Package _package;
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.package != null) {
|
||||||
|
_package = widget.package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
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.edit.title")),
|
||||||
|
),
|
||||||
|
body: Card(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
223
lib/fcs/common/pages/package/package_list.dart
Normal file
223
lib/fcs/common/pages/package/package_list.dart
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
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/widgets/bottom_up_page_route.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class PackageList extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_PackageListState createState() => _PackageListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PackageListState extends State<PackageList> {
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var packageModel = Provider.of<PackageModel>(context);
|
||||||
|
|
||||||
|
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.title")),
|
||||||
|
actions: <Widget>[
|
||||||
|
// IconButton(
|
||||||
|
// icon: Icon(
|
||||||
|
// Ionicons.ios_barcode,
|
||||||
|
// color: Colors.white,
|
||||||
|
// ),
|
||||||
|
// iconSize: 30,
|
||||||
|
// onPressed: () {
|
||||||
|
// Navigator.push(
|
||||||
|
// context,
|
||||||
|
// BottomUpPageRoute(BarcodeScreenPage()),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
iconSize: 30,
|
||||||
|
onPressed: () => searchUser(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () {
|
||||||
|
_newPickup();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
label:
|
||||||
|
Text(AppTranslations.of(context).text("package.create.title")),
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
),
|
||||||
|
body: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: packageModel.getPackages.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return PackageListRow(
|
||||||
|
package: packageModel.getPackages[index],
|
||||||
|
isReadOnly: false,
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_newPickup() {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
BottomUpPageRoute(PackageNew()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _upComing() {
|
||||||
|
var packageModel = Provider.of<PackageModel>(context);
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: packageModel.upcoming.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return PackageListRow(
|
||||||
|
package: packageModel.upcoming[index],
|
||||||
|
isReadOnly: false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _completed() {
|
||||||
|
var packageModel = Provider.of<PackageModel>(context);
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: packageModel.completed.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return PackageListRow(
|
||||||
|
package: packageModel.completed[index],
|
||||||
|
isReadOnly: false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _shipped() {
|
||||||
|
var packageModel = Provider.of<PackageModel>(context);
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: packageModel.shipped.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return PackageListRow(
|
||||||
|
package: packageModel.shipped[index],
|
||||||
|
isReadOnly: false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _arrived() {
|
||||||
|
var packageModel = Provider.of<PackageModel>(context);
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: packageModel.arrived.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return PackageListRow(
|
||||||
|
package: packageModel.arrived[index],
|
||||||
|
isReadOnly: false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _delivered() {
|
||||||
|
var packageModel = Provider.of<PackageModel>(context);
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: packageModel.delivered.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return PackageListRow(
|
||||||
|
package: packageModel.delivered[index],
|
||||||
|
isReadOnly: false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
132
lib/fcs/common/pages/package/package_list_row.dart
Normal file
132
lib/fcs/common/pages/package/package_list_row.dart
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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_info.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
||||||
|
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});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PackageListRowtate createState() => _PackageListRowtate();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PackageListRowtate extends State<PackageListRow> {
|
||||||
|
final double dotSize = 15.0;
|
||||||
|
Package _package = new Package();
|
||||||
|
final DateFormat dateFormat = new DateFormat("dd MMM yyyy");
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_package = widget.package;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
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)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: new Row(
|
||||||
|
children: <Widget>[
|
||||||
|
new Expanded(
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: new Text(
|
||||||
|
_package.id == null ? '' : _package.trackingID,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: 15.0, color: Colors.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: new Text(
|
||||||
|
_package.market == null ? '' : _package.market,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: 15.0, color: Colors.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
// child: new Text(
|
||||||
|
// _package.trackingID == null
|
||||||
|
// ? ''
|
||||||
|
// : _package.trackingID,
|
||||||
|
// style: new TextStyle(
|
||||||
|
// fontSize: 15.0, color: Colors.grey),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(3.0),
|
||||||
|
child: getStatus(_package.status),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: new Text(
|
||||||
|
dateFormat.format(_package.arrivedDate),
|
||||||
|
style: new TextStyle(fontSize: 15.0, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(left: 8.0, top: 5, bottom: 5),
|
||||||
|
// child: Row(
|
||||||
|
// children: <Widget>[
|
||||||
|
// new Text(
|
||||||
|
// _package.weight == null
|
||||||
|
// ? ''
|
||||||
|
// : _package.weight.toString() + 'lb - ',
|
||||||
|
// style:
|
||||||
|
// new TextStyle(fontSize: 15.0, color: Colors.grey),
|
||||||
|
// ),
|
||||||
|
// new Text(
|
||||||
|
// _package.price == null
|
||||||
|
// ? ""
|
||||||
|
// : "\$ " + _package.price.toString(),
|
||||||
|
// style:
|
||||||
|
// new TextStyle(fontSize: 15.0, color: Colors.grey),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
221
lib/fcs/common/pages/package/package_new.dart
Normal file
221
lib/fcs/common/pages/package/package_new.dart
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
import 'package:barcode_scan/barcode_scan.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/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/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/display_text.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
typedef void FindCallBack();
|
||||||
|
|
||||||
|
class PackageNew extends StatefulWidget {
|
||||||
|
const PackageNew();
|
||||||
|
@override
|
||||||
|
_PackageNewState createState() => _PackageNewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PackageNewState extends State<PackageNew> {
|
||||||
|
TextEditingController _phoneInput = new TextEditingController();
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
User user;
|
||||||
|
|
||||||
|
List<Package> packages = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var fcsIDBox = Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: DisplayText(
|
||||||
|
text: user != null ? user.fcsID : "",
|
||||||
|
labelText: getLocalString(context, "package.create.fcs.id"),
|
||||||
|
iconData: Icons.perm_identity,
|
||||||
|
)),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.search, color: primaryColor),
|
||||||
|
onPressed: () => searchUser(context, callbackUserSelect: (u) {
|
||||||
|
setState(() {
|
||||||
|
this.user = u;
|
||||||
|
});
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final namebox = DisplayText(
|
||||||
|
text: user != null ? user.name : "",
|
||||||
|
labelText: getLocalString(context, "package.create.name"),
|
||||||
|
iconData: Icons.person,
|
||||||
|
);
|
||||||
|
final phoneNumberBox = DisplayText(
|
||||||
|
text: user != null ? user.phoneNumber : "",
|
||||||
|
labelText: getLocalString(context, "package.create.phone"),
|
||||||
|
iconData: Icons.phone,
|
||||||
|
);
|
||||||
|
final createButton = fcsButton(
|
||||||
|
context,
|
||||||
|
getLocalString(context, 'package.create.packages'),
|
||||||
|
callack: _create,
|
||||||
|
);
|
||||||
|
final packageList = Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: new List.generate(
|
||||||
|
this.packages.length, (index) => _packageItem(context, index))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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.create.title",
|
||||||
|
fontSize: 20,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12.0, right: 12),
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
fcsIDBox,
|
||||||
|
phoneNumberBox,
|
||||||
|
namebox,
|
||||||
|
Divider(),
|
||||||
|
Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text("Packages (${packages.length})"),
|
||||||
|
SizedBox(
|
||||||
|
width: 30,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
onPressed: _scan,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
packageList,
|
||||||
|
Divider(),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
createButton,
|
||||||
|
SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scan() async {
|
||||||
|
PermissionStatus permission =
|
||||||
|
await PermissionHandler().checkPermissionStatus(PermissionGroup.camera);
|
||||||
|
if (permission != PermissionStatus.granted) {
|
||||||
|
Map<PermissionGroup, PermissionStatus> permissions =
|
||||||
|
await PermissionHandler()
|
||||||
|
.requestPermissions([PermissionGroup.camera]);
|
||||||
|
if (permissions[PermissionGroup.camera] != PermissionStatus.granted) {
|
||||||
|
showMsgDialog(context, "Error", "Camera permission is not granted");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String barcode;
|
||||||
|
try {
|
||||||
|
barcode = await BarcodeScanner.scan();
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
if (e.code == BarcodeScanner.CameraAccessDenied) {
|
||||||
|
print('The user did not grant the camera permission!');
|
||||||
|
} else {
|
||||||
|
print('Unknown error: $e');
|
||||||
|
}
|
||||||
|
} on FormatException {
|
||||||
|
print(
|
||||||
|
'null (User returned using the "back"-button before scanning anything. Result)');
|
||||||
|
} catch (e) {
|
||||||
|
print('Unknown error: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (barcode != null) {
|
||||||
|
if (packages.any((e) => e.trackingID == barcode)) {
|
||||||
|
showMsgDialog(context, "Error", "Already scanned!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
packages.add(Package(trackingID: barcode));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _packageItem(BuildContext context, int index) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DisplayText(
|
||||||
|
labelText: "Tracking ID",
|
||||||
|
text: packages[index].trackingID,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.remove,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
packages.removeAt(index);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_create() async {
|
||||||
|
if (user == null || packages == null || packages.length == 0) {
|
||||||
|
showMsgDialog(context, "Error", "Invalid user!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
StaffModel staffModel = Provider.of<StaffModel>(context, listen: false);
|
||||||
|
try {
|
||||||
|
// await staffModel.updatePrivileges(this.selectedUser.id, privilegesIDs());
|
||||||
|
Navigator.pop(context);
|
||||||
|
} catch (e) {
|
||||||
|
showMsgDialog(context, "Error", e.toString());
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
lib/fcs/common/pages/package/search_page.dart
Normal file
111
lib/fcs/common/pages/package/search_page.dart
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// 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,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
139
lib/fcs/common/pages/package/shipping_address_editor.dart
Normal file
139
lib/fcs/common/pages/package/shipping_address_editor.dart
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/vo/shipping_address.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
||||||
|
import 'package:flutter_icons/flutter_icons.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ShippingAddressEditor extends StatefulWidget {
|
||||||
|
final ShippingAddress shippingAddress;
|
||||||
|
ShippingAddressEditor({this.shippingAddress});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ShippingAddressEditorState createState() => _ShippingAddressEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShippingAddressEditorState extends State<ShippingAddressEditor> {
|
||||||
|
TextEditingController _nameController = new TextEditingController();
|
||||||
|
TextEditingController _address1Controller = new TextEditingController();
|
||||||
|
TextEditingController _address2Controller = new TextEditingController();
|
||||||
|
TextEditingController _cityController = new TextEditingController();
|
||||||
|
TextEditingController _stateController = new TextEditingController();
|
||||||
|
TextEditingController _phoneController = new TextEditingController();
|
||||||
|
|
||||||
|
ShippingAddress _shippingAddress = new ShippingAddress();
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.shippingAddress != null) {
|
||||||
|
_shippingAddress = widget.shippingAddress;
|
||||||
|
_nameController.text = _shippingAddress.fullName;
|
||||||
|
_address1Controller.text = _shippingAddress.addressLine1;
|
||||||
|
_address2Controller.text = _shippingAddress.addressLine2;
|
||||||
|
_cityController.text = _shippingAddress.city;
|
||||||
|
_stateController.text = _shippingAddress.state;
|
||||||
|
_phoneController.text = _shippingAddress.phoneNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final usaAddress =
|
||||||
|
fcsInput('Full Name', Icons.text_format, controller: _nameController);
|
||||||
|
final mmAddress = fcsInput('Address Line 1', Icons.location_on,
|
||||||
|
controller: _address1Controller);
|
||||||
|
|
||||||
|
final contactNumber = fcsInput('Address Line 2', Icons.location_on,
|
||||||
|
controller: _address2Controller);
|
||||||
|
final mmContactNumber =
|
||||||
|
fcsInput('City', Icons.location_city, controller: _cityController);
|
||||||
|
|
||||||
|
final mailBox =
|
||||||
|
fcsInput('State/Region', Entypo.location, controller: _stateController);
|
||||||
|
final fbLinkBox =
|
||||||
|
fcsInput('Phone Number', Icons.phone, controller: _phoneController);
|
||||||
|
|
||||||
|
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: LocalText(
|
||||||
|
context,
|
||||||
|
'user.form.shipping_address',
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Card(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0, right: 10),
|
||||||
|
child: ListView(children: <Widget>[
|
||||||
|
usaAddress,
|
||||||
|
SizedBox(height: 10),
|
||||||
|
mmAddress,
|
||||||
|
SizedBox(height: 10),
|
||||||
|
contactNumber,
|
||||||
|
SizedBox(height: 10),
|
||||||
|
mmContactNumber,
|
||||||
|
SizedBox(height: 10),
|
||||||
|
mailBox,
|
||||||
|
SizedBox(height: 10),
|
||||||
|
fbLinkBox,
|
||||||
|
SizedBox(height: 10),
|
||||||
|
]),
|
||||||
|
)),
|
||||||
|
widget.shippingAddress == null
|
||||||
|
? Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 250,
|
||||||
|
child: FlatButton(
|
||||||
|
child: Text('Create'),
|
||||||
|
color: primaryColor,
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)))
|
||||||
|
: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 250,
|
||||||
|
child: FlatButton(
|
||||||
|
child: Text('Update'),
|
||||||
|
color: primaryColor,
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
SizedBox(height: 10)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
lib/fcs/common/pages/package/shipping_address_list.dart
Normal file
105
lib/fcs/common/pages/package/shipping_address_list.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/model/shipment_model.dart';
|
||||||
|
|
||||||
|
import 'package:fcs/pages/search_page.dart';
|
||||||
|
import 'package:fcs/pages_fcs/shipping_address_row.dart';
|
||||||
|
import 'package:fcs/vo/shipping_address.dart';
|
||||||
|
import 'package:fcs/widget/bottom_up_page_route.dart';
|
||||||
|
import 'package:fcs/widget/localization/app_translations.dart';
|
||||||
|
import 'package:fcs/widget/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class ShippingAddressList extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_ShippingAddressListState createState() => _ShippingAddressListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShippingAddressListState extends State<ShippingAddressList> {
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var shipmentModel = Provider.of<ShipmentModel>(context);
|
||||||
|
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("shipping_addresses")),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
iconSize: 30,
|
||||||
|
onPressed: () => showPlacesSearch(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children:
|
||||||
|
getAddressList(context, shipmentModel.shippingAddresses),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
BottomUpPageRoute(ShippingAddressList()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
label: Text(
|
||||||
|
'Add New\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.pop(context, s.value);
|
||||||
|
},
|
||||||
|
child: ShippingAddressRow(shippingAddress: s.value, index: s.key),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
121
lib/fcs/common/pages/package/shipping_address_row.dart
Normal file
121
lib/fcs/common/pages/package/shipping_address_row.dart
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/vo/shipping_address.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/model/shipment_model.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class ShippingAddressRow extends StatelessWidget {
|
||||||
|
final ShippingAddress shippingAddress;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
const ShippingAddressRow({Key key, this.shippingAddress, this.index})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var shipmentModel = Provider.of<ShipmentModel>(context);
|
||||||
|
return 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>[
|
||||||
|
// Padding(
|
||||||
|
// padding: EdgeInsets.all(5.0),
|
||||||
|
// child: Icon(
|
||||||
|
// SimpleLineIcons.location_pin,
|
||||||
|
// color: primaryColor,
|
||||||
|
// )),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// IconButton(
|
||||||
|
// padding: EdgeInsets.only(right: 30),
|
||||||
|
// icon: Icon(Icons.delete, color: Colors.black45),
|
||||||
|
// onPressed: null)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
index == null
|
||||||
|
? Container()
|
||||||
|
: index == shipmentModel.shippingAddresses.length - 1
|
||||||
|
? Container()
|
||||||
|
: Divider(color: Colors.black)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
lib/fcs/common/pages/package/user_list_row.dart
Normal file
86
lib/fcs/common/pages/package/user_list_row.dart
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
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:flutter/material.dart';
|
||||||
|
|
||||||
|
class UserListRow extends StatefulWidget {
|
||||||
|
final CallbackUserSelect callbackUserSelect;
|
||||||
|
final User user;
|
||||||
|
const UserListRow({this.user, this.callbackUserSelect});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_UserListRowState createState() => _UserListRowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserListRowState extends State<UserListRow> {
|
||||||
|
final double dotSize = 15.0;
|
||||||
|
User user;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
this.user = widget.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.pop(context);
|
||||||
|
if (widget.callbackUserSelect != null)
|
||||||
|
widget.callbackUserSelect(widget.user);
|
||||||
|
},
|
||||||
|
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(
|
||||||
|
user.name == null ? '' : user.name,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: 15.0, color: Colors.black),
|
||||||
|
),
|
||||||
|
new Text(
|
||||||
|
user.fcsID == null ? "" : user.fcsID,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: 13.0, color: Colors.grey),
|
||||||
|
),
|
||||||
|
new Text(
|
||||||
|
user.phoneNumber == null ? "" : user.phoneNumber,
|
||||||
|
style: new TextStyle(
|
||||||
|
fontSize: 13.0, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
119
lib/fcs/common/pages/package/user_serach.dart
Normal file
119
lib/fcs/common/pages/package/user_serach.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
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:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
typedef CallbackUserSelect(User suer);
|
||||||
|
|
||||||
|
Future<User> searchUser(BuildContext context,
|
||||||
|
{CallbackUserSelect callbackUserSelect}) async =>
|
||||||
|
await showSearch<User>(
|
||||||
|
context: context,
|
||||||
|
delegate: UserSearchDelegate(callbackUserSelect: callbackUserSelect),
|
||||||
|
);
|
||||||
|
|
||||||
|
class UserSearchDelegate extends SearchDelegate<User> {
|
||||||
|
final CallbackUserSelect callbackUserSelect;
|
||||||
|
|
||||||
|
UserSearchDelegate({this.callbackUserSelect});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchFieldLabel => 'Enter FCS ID or Name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
ThemeData appBarTheme(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return theme.copyWith(
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: theme.primaryTextTheme.caption.color, fontSize: 14)),
|
||||||
|
textTheme: theme.textTheme.copyWith(
|
||||||
|
title: theme.textTheme.title.copyWith(
|
||||||
|
color: theme.primaryTextTheme.title.color, fontSize: 16)),
|
||||||
|
primaryColor: primaryColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 packageModel = Provider.of<PackageModel>(context);
|
||||||
|
return FutureBuilder(
|
||||||
|
future: packageModel.searchUser(query),
|
||||||
|
builder: (context, AsyncSnapshot<List<User>> snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
if (snapshot.data.length == 0) {
|
||||||
|
return Container(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"No result found",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(top: 15),
|
||||||
|
child: ListView(
|
||||||
|
children: snapshot.data
|
||||||
|
.map((u) => UserListRow(
|
||||||
|
user: u,
|
||||||
|
callbackUserSelect: callbackUserSelect,
|
||||||
|
))
|
||||||
|
.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,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,13 +73,9 @@ class StaffModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updatePrivileges(String userID, List<String> privileges) async {
|
Future<void> updatePrivileges(String userID, List<String> privileges) async {
|
||||||
try {
|
|
||||||
await request("/employee/privileges", "PUT",
|
await request("/employee/privileges", "PUT",
|
||||||
payload: {"id": userID, "privileges": privileges},
|
payload: {"id": userID, "privileges": privileges},
|
||||||
token: await getToken());
|
token: await getToken());
|
||||||
} catch (e) {
|
|
||||||
throw Exception(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> findUser(String phoneNumber) {
|
Future<User> findUser(String phoneNumber) {
|
||||||
|
|||||||
@@ -88,9 +88,12 @@ class _StaffEditorState extends State<StaffEditor> {
|
|||||||
Widget phoneSearchbox(BuildContext context, FindCallBack findCallBack) {
|
Widget phoneSearchbox(BuildContext context, FindCallBack findCallBack) {
|
||||||
var languageModel = Provider.of<LanguageModel>(context);
|
var languageModel = Provider.of<LanguageModel>(context);
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(bottom: 15, left: 8),
|
padding: EdgeInsets.only(
|
||||||
|
bottom: 25,
|
||||||
|
left: 8,
|
||||||
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: const Alignment(1.2, 1.0),
|
alignment: const Alignment(1, 1.0),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _phoneInput,
|
controller: _phoneInput,
|
||||||
@@ -99,26 +102,24 @@ class _StaffEditorState extends State<StaffEditor> {
|
|||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
decoration: new InputDecoration(
|
decoration: new InputDecoration(
|
||||||
labelText: AppTranslations.of(context).text('employee.phone'),
|
labelText:
|
||||||
|
AppTranslations.of(context).text('staff.phone.search'),
|
||||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||||
icon: Icon(
|
// icon: Icon(
|
||||||
Icons.phone,
|
// Icons.search,
|
||||||
color: primaryColor,
|
// color: primaryColor,
|
||||||
),
|
// ),
|
||||||
enabledBorder: UnderlineInputBorder(
|
enabledBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
new FlatButton(
|
new IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
findCallBack();
|
findCallBack();
|
||||||
},
|
},
|
||||||
child: new Icon(
|
icon: new Icon(Icons.search, size: 25, color: primaryColor))
|
||||||
Icons.search,
|
|
||||||
size: 25,
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,9 @@ class _StaffEditorState extends State<StaffEditor> {
|
|||||||
labelText: getLocalString(context, "customer.phone"),
|
labelText: getLocalString(context, "customer.phone"),
|
||||||
iconData: Icons.phone,
|
iconData: Icons.phone,
|
||||||
)),
|
)),
|
||||||
IconButton(
|
isNew
|
||||||
|
? Container()
|
||||||
|
: IconButton(
|
||||||
icon: Icon(Icons.open_in_new, color: primaryColor),
|
icon: Icon(Icons.open_in_new, color: primaryColor),
|
||||||
onPressed: () => call(context, user.phoneNumber)),
|
onPressed: () => call(context, user.phoneNumber)),
|
||||||
],
|
],
|
||||||
@@ -174,21 +177,17 @@ class _StaffEditorState extends State<StaffEditor> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.only(left: 12.0, right: 12),
|
||||||
child: Column(
|
child: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
isNew
|
isNew
|
||||||
? phoneSearchbox(context, () => _findUser(context))
|
? phoneSearchbox(context, () => _findUser(context))
|
||||||
: Container(),
|
: Container(),
|
||||||
phoneNumberBox,
|
phoneNumberBox,
|
||||||
namebox,
|
namebox,
|
||||||
Expanded(
|
Column(
|
||||||
child: ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
|
||||||
children: showprivilegeList(context),
|
children: showprivilegeList(context),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Container(
|
Container(
|
||||||
child: isNew ? addButton : updateButton,
|
child: isNew ? addButton : updateButton,
|
||||||
),
|
),
|
||||||
@@ -246,6 +245,7 @@ class _StaffEditorState extends State<StaffEditor> {
|
|||||||
|
|
||||||
_findUser(BuildContext context) async {
|
_findUser(BuildContext context) async {
|
||||||
StaffModel staffModel = Provider.of<StaffModel>(context, listen: false);
|
StaffModel staffModel = Provider.of<StaffModel>(context, listen: false);
|
||||||
|
if (_phoneInput.text == "") return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class _StaffListState extends State<StaffList> {
|
|||||||
Navigator.of(context).push(BottomUpPageRoute(StaffEditor()));
|
Navigator.of(context).push(BottomUpPageRoute(StaffEditor()));
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.add),
|
icon: Icon(Icons.add),
|
||||||
label: Text(AppTranslations.of(context).text("staff.new")),
|
label: LocalText(context, "staff.new", color: Colors.white),
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
),
|
),
|
||||||
body: new ListView.separated(
|
body: new ListView.separated(
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ class TermModel extends BaseModel {
|
|||||||
|
|
||||||
Future<void> saveTerm(Term term) async {
|
Future<void> saveTerm(Term term) async {
|
||||||
await request("/terms", "PUT",
|
await request("/terms", "PUT",
|
||||||
payload: term.toMap(), token: await Services.instance.authService.getToken());
|
payload: term.toMap(),
|
||||||
notifyListeners();
|
token: await Services.instance.authService.getToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ import 'dart:convert';
|
|||||||
import 'package:fcs/fcs/common/domain/vo/term.dart';
|
import 'package:fcs/fcs/common/domain/vo/term.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/theme.dart';
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
import 'package:fcs/fcs/common/pages/term/model/term_model.dart';
|
import 'package:fcs/fcs/common/pages/term/model/term_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
|
||||||
import 'package:fcs/model/main_model.dart';
|
|
||||||
import 'package:fcs/fcs/common/pages/util.dart';
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
import 'package:fcs/widget/progress.dart';
|
import 'package:fcs/widget/progress.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:zefyr/zefyr.dart';
|
import 'package:zefyr/zefyr.dart';
|
||||||
|
|
||||||
@@ -55,6 +53,8 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final savebtn =
|
||||||
|
fcsButton(context, getLocalString(context, "btn.save"), callack: _save);
|
||||||
return LocalProgress(
|
return LocalProgress(
|
||||||
inAsyncCall: _isLoading,
|
inAsyncCall: _isLoading,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@@ -72,16 +72,12 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: primaryColor,
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.save),
|
|
||||||
onPressed: () {
|
|
||||||
_save();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: ZefyrScaffold(
|
body: ListView(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: MediaQuery.of(context).size.height - 150,
|
||||||
|
child: ZefyrScaffold(
|
||||||
child: ZefyrTheme(
|
child: ZefyrTheme(
|
||||||
data: ZefyrThemeData().copyWith(),
|
data: ZefyrThemeData().copyWith(),
|
||||||
child: ZefyrEditor(
|
child: ZefyrEditor(
|
||||||
@@ -92,6 +88,13 @@ class _TermEditState extends State<TermEdit> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
savebtn,
|
||||||
|
SizedBox(
|
||||||
|
height: 10,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,10 +100,11 @@ Future<void> showConfirmDialog(
|
|||||||
),
|
),
|
||||||
content: Container(
|
content: Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
|
color: Colors.grey[300],
|
||||||
child: Text(
|
child: Text(
|
||||||
AppTranslations.of(context).text('Cancel'),
|
AppTranslations.of(context).text('Cancel'),
|
||||||
style: Provider.of<LanguageModel>(context).isEng
|
style: Provider.of<LanguageModel>(context).isEng
|
||||||
@@ -113,6 +114,9 @@ Future<void> showConfirmDialog(
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}),
|
}),
|
||||||
|
SizedBox(
|
||||||
|
width: 0,
|
||||||
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
child: Text(AppTranslations.of(context).text('Ok'),
|
child: Text(AppTranslations.of(context).text('Ok'),
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ class DisplayText extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
labelText == null
|
||||||
|
? Container()
|
||||||
|
: Text(
|
||||||
labelText,
|
labelText,
|
||||||
style: labelStyle,
|
style: labelStyle,
|
||||||
),
|
),
|
||||||
|
|||||||
68
lib/fcs/common/pages/widgets/fcs_expansion_tile.dart
Normal file
68
lib/fcs/common/pages/widgets/fcs_expansion_tile.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/widget/multi_img_controller.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FcsExpansionTile extends StatefulWidget {
|
||||||
|
final ValueChanged<bool> onExpansionChanged;
|
||||||
|
final CallBack onEditPress;
|
||||||
|
final List<Widget> children;
|
||||||
|
final Widget title;
|
||||||
|
final bool isEdit;
|
||||||
|
const FcsExpansionTile(
|
||||||
|
{this.onExpansionChanged,
|
||||||
|
this.children,
|
||||||
|
this.title,
|
||||||
|
this.isEdit = false,
|
||||||
|
this.onEditPress});
|
||||||
|
@override
|
||||||
|
_FcsExpansionTileState createState() => _FcsExpansionTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FcsExpansionTileState extends State<FcsExpansionTile> {
|
||||||
|
bool expanded;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
this.expanded = false;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Theme(
|
||||||
|
data: ThemeData(
|
||||||
|
accentColor: secondaryColor, dividerColor: Colors.transparent),
|
||||||
|
child: ExpansionTile(
|
||||||
|
onExpansionChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
expanded = value;
|
||||||
|
});
|
||||||
|
if (widget.onExpansionChanged != null)
|
||||||
|
widget.onExpansionChanged(value);
|
||||||
|
},
|
||||||
|
title: widget.title,
|
||||||
|
children: widget.children,
|
||||||
|
trailing: widget.isEdit
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () =>
|
||||||
|
widget.onEditPress != null ? widget.onEditPress() : {},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.edit,
|
||||||
|
color: primaryColor,
|
||||||
|
))
|
||||||
|
: AnimatedSwitcher(
|
||||||
|
child: expanded
|
||||||
|
? Icon(
|
||||||
|
Icons.remove,
|
||||||
|
color: primaryColor,
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
duration: Duration(seconds: 2)),
|
||||||
|
childrenPadding: EdgeInsets.all(18),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ class InputText extends StatelessWidget {
|
|||||||
decoration: new InputDecoration(
|
decoration: new InputDecoration(
|
||||||
// hintText: '',
|
// hintText: '',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
height: 3.5,
|
height: 1.5,
|
||||||
),
|
),
|
||||||
labelText: labelTextKey == null
|
labelText: labelTextKey == null
|
||||||
? null
|
? null
|
||||||
|
|||||||
55
lib/fcs/common/pages/widgets/label_widgets.dart
Normal file
55
lib/fcs/common/pages/widgets/label_widgets.dart
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/widget/img_url.dart';
|
||||||
|
import 'package:fcs/widget/local_text.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
Widget labeledText(BuildContext context, String text, String label,
|
||||||
|
{bool number = false}) {
|
||||||
|
final w = Container(
|
||||||
|
padding: EdgeInsets.only(top: 3, left: 10, bottom: 3, right: 10),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: LocalText(
|
||||||
|
context,
|
||||||
|
label,
|
||||||
|
fontSize: 14,
|
||||||
|
color: primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// number ? Spacer() : Container(),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: 10),
|
||||||
|
// alignment: number ? Alignment.topRight : null,
|
||||||
|
child: Text(
|
||||||
|
text == null ? "" : text,
|
||||||
|
style: textStyle,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget labeledImg(BuildContext context, String imgUrl, String label) {
|
||||||
|
final _labeledImg = Container(
|
||||||
|
padding: EdgeInsets.only(left: 10),
|
||||||
|
child: Row(children: <Widget>[
|
||||||
|
LocalText(context, label),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: ImageUrl(
|
||||||
|
url: imgUrl,
|
||||||
|
title: "Image",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
return _labeledImg;
|
||||||
|
}
|
||||||
926
lib/fcs/common/pages/widgets/my_data_table.dart
Normal file
926
lib/fcs/common/pages/widgets/my_data_table.dart
Normal file
@@ -0,0 +1,926 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
/// Signature for [MyDataColumn.onSort] callback.
|
||||||
|
typedef MyDataColumnSortCallback = void Function(
|
||||||
|
int columnIndex, bool ascending);
|
||||||
|
|
||||||
|
/// Column configuration for a [MyDataTable].
|
||||||
|
///
|
||||||
|
/// One column configuration must be provided for each column to
|
||||||
|
/// display in the table. The list of [MyDataColumn] objects is passed
|
||||||
|
/// as the `columns` argument to the [new MyDataTable] constructor.
|
||||||
|
@immutable
|
||||||
|
class MyDataColumn {
|
||||||
|
/// Creates the configuration for a column of a [MyDataTable].
|
||||||
|
///
|
||||||
|
/// The [label] argument must not be null.
|
||||||
|
const MyDataColumn({
|
||||||
|
@required this.label,
|
||||||
|
this.tooltip,
|
||||||
|
this.numeric = false,
|
||||||
|
this.onSort,
|
||||||
|
}) : assert(label != null);
|
||||||
|
|
||||||
|
/// The column heading.
|
||||||
|
///
|
||||||
|
/// Typically, this will be a [Text] widget. It could also be an
|
||||||
|
/// [Icon] (typically using size 18), or a [Row] with an icon and
|
||||||
|
/// some text.
|
||||||
|
///
|
||||||
|
/// The label should not include the sort indicator.
|
||||||
|
final Widget label;
|
||||||
|
|
||||||
|
/// The column heading's tooltip.
|
||||||
|
///
|
||||||
|
/// This is a longer description of the column heading, for cases
|
||||||
|
/// where the heading might have been abbreviated to keep the column
|
||||||
|
/// width to a reasonable size.
|
||||||
|
final String tooltip;
|
||||||
|
|
||||||
|
/// Whether this column represents numeric data or not.
|
||||||
|
///
|
||||||
|
/// The contents of cells of columns containing numeric data are
|
||||||
|
/// right-aligned.
|
||||||
|
final bool numeric;
|
||||||
|
|
||||||
|
/// Called when the user asks to sort the table using this column.
|
||||||
|
///
|
||||||
|
/// If null, the column will not be considered sortable.
|
||||||
|
///
|
||||||
|
/// See [MyDataTable.sortColumnIndex] and [MyDataTable.sortAscending].
|
||||||
|
final MyDataColumnSortCallback onSort;
|
||||||
|
|
||||||
|
bool get _debugInteractive => onSort != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Row configuration and cell data for a [MyDataTable].
|
||||||
|
///
|
||||||
|
/// One row configuration must be provided for each row to
|
||||||
|
/// display in the table. The list of [MyDataRow] objects is passed
|
||||||
|
/// as the `rows` argument to the [new MyDataTable] constructor.
|
||||||
|
///
|
||||||
|
/// The data for this row of the table is provided in the [cells]
|
||||||
|
/// property of the [MyDataRow] object.
|
||||||
|
@immutable
|
||||||
|
class MyDataRow {
|
||||||
|
/// Creates the configuration for a row of a [MyDataTable].
|
||||||
|
///
|
||||||
|
/// The [cells] argument must not be null.
|
||||||
|
const MyDataRow({
|
||||||
|
this.key,
|
||||||
|
this.selected = false,
|
||||||
|
this.onSelectChanged,
|
||||||
|
@required this.cells,
|
||||||
|
}) : assert(cells != null);
|
||||||
|
|
||||||
|
/// Creates the configuration for a row of a [MyDataTable], deriving
|
||||||
|
/// the key from a row index.
|
||||||
|
///
|
||||||
|
/// The [cells] argument must not be null.
|
||||||
|
MyDataRow.byIndex({
|
||||||
|
int index,
|
||||||
|
this.selected = false,
|
||||||
|
this.onSelectChanged,
|
||||||
|
@required this.cells,
|
||||||
|
}) : assert(cells != null),
|
||||||
|
key = ValueKey<int>(index);
|
||||||
|
|
||||||
|
/// A [Key] that uniquely identifies this row. This is used to
|
||||||
|
/// ensure that if a row is added or removed, any stateful widgets
|
||||||
|
/// related to this row (e.g. an in-progress checkbox animation)
|
||||||
|
/// remain on the right row visually.
|
||||||
|
///
|
||||||
|
/// If the table never changes once created, no key is necessary.
|
||||||
|
final LocalKey key;
|
||||||
|
|
||||||
|
/// Called when the user selects or unselects a selectable row.
|
||||||
|
///
|
||||||
|
/// If this is not null, then the row is selectable. The current
|
||||||
|
/// selection state of the row is given by [selected].
|
||||||
|
///
|
||||||
|
/// If any row is selectable, then the table's heading row will have
|
||||||
|
/// a checkbox that can be checked to select all selectable rows
|
||||||
|
/// (and which is checked if all the rows are selected), and each
|
||||||
|
/// subsequent row will have a checkbox to toggle just that row.
|
||||||
|
///
|
||||||
|
/// A row whose [onSelectChanged] callback is null is ignored for
|
||||||
|
/// the purposes of determining the state of the "all" checkbox,
|
||||||
|
/// and its checkbox is disabled.
|
||||||
|
final ValueChanged<bool> onSelectChanged;
|
||||||
|
|
||||||
|
/// Whether the row is selected.
|
||||||
|
///
|
||||||
|
/// If [onSelectChanged] is non-null for any row in the table, then
|
||||||
|
/// a checkbox is shown at the start of each row. If the row is
|
||||||
|
/// selected (true), the checkbox will be checked and the row will
|
||||||
|
/// be highlighted.
|
||||||
|
///
|
||||||
|
/// Otherwise, the checkbox, if present, will not be checked.
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
|
/// The data for this row.
|
||||||
|
///
|
||||||
|
/// There must be exactly as many cells as there are columns in the
|
||||||
|
/// table.
|
||||||
|
final List<MyDataCell> cells;
|
||||||
|
|
||||||
|
bool get _debugInteractive =>
|
||||||
|
onSelectChanged != null ||
|
||||||
|
cells.any((MyDataCell cell) => cell._debugInteractive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The data for a cell of a [MyDataTable].
|
||||||
|
///
|
||||||
|
/// One list of [MyDataCell] objects must be provided for each [MyDataRow]
|
||||||
|
/// in the [MyDataTable], in the [new MyDataRow] constructor's `cells`
|
||||||
|
/// argument.
|
||||||
|
@immutable
|
||||||
|
class MyDataCell {
|
||||||
|
/// Creates an object to hold the data for a cell in a [MyDataTable].
|
||||||
|
///
|
||||||
|
/// The first argument is the widget to show for the cell, typically
|
||||||
|
/// a [Text] or [DropdownButton] widget; this becomes the [child]
|
||||||
|
/// property and must not be null.
|
||||||
|
///
|
||||||
|
/// If the cell has no data, then a [Text] widget with placeholder
|
||||||
|
/// text should be provided instead, and then the [placeholder]
|
||||||
|
/// argument should be set to true.
|
||||||
|
const MyDataCell(
|
||||||
|
this.child, {
|
||||||
|
this.placeholder = false,
|
||||||
|
this.showEditIcon = false,
|
||||||
|
this.onTap,
|
||||||
|
}) : assert(child != null);
|
||||||
|
|
||||||
|
/// A cell that has no content and has zero width and height.
|
||||||
|
static final MyDataCell empty =
|
||||||
|
MyDataCell(Container(width: 0.0, height: 0.0));
|
||||||
|
|
||||||
|
/// The data for the row.
|
||||||
|
///
|
||||||
|
/// Typically a [Text] widget or a [DropdownButton] widget.
|
||||||
|
///
|
||||||
|
/// If the cell has no data, then a [Text] widget with placeholder
|
||||||
|
/// text should be provided instead, and [placeholder] should be set
|
||||||
|
/// to true.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.child}
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// Whether the [child] is actually a placeholder.
|
||||||
|
///
|
||||||
|
/// If this is true, the default text style for the cell is changed
|
||||||
|
/// to be appropriate for placeholder text.
|
||||||
|
final bool placeholder;
|
||||||
|
|
||||||
|
/// Whether to show an edit icon at the end of the cell.
|
||||||
|
///
|
||||||
|
/// This does not make the cell actually editable; the caller must
|
||||||
|
/// implement editing behavior if desired (initiated from the
|
||||||
|
/// [onTap] callback).
|
||||||
|
///
|
||||||
|
/// If this is set, [onTap] should also be set, otherwise tapping
|
||||||
|
/// the icon will have no effect.
|
||||||
|
final bool showEditIcon;
|
||||||
|
|
||||||
|
/// Called if the cell is tapped.
|
||||||
|
///
|
||||||
|
/// If non-null, tapping the cell will call this callback. If
|
||||||
|
/// null, tapping the cell will attempt to select the row (if
|
||||||
|
/// [MyDataRow.onSelectChanged] is provided).
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
bool get _debugInteractive => onTap != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A material design data table.
|
||||||
|
///
|
||||||
|
/// {@youtube 560 315 https://www.youtube.com/watch?v=ktTajqbhIcY}
|
||||||
|
///
|
||||||
|
/// Displaying data in a table is expensive, because to lay out the
|
||||||
|
/// table all the data must be measured twice, once to negotiate the
|
||||||
|
/// dimensions to use for each column, and once to actually lay out
|
||||||
|
/// the table given the results of the negotiation.
|
||||||
|
///
|
||||||
|
/// For this reason, if you have a lot of data (say, more than a dozen
|
||||||
|
/// rows with a dozen columns, though the precise limits depend on the
|
||||||
|
/// target device), it is suggested that you use a
|
||||||
|
/// [PaginatedMyDataTable] which automatically splits the data into
|
||||||
|
/// multiple pages.
|
||||||
|
///
|
||||||
|
/// {@tool snippet --template=stateless_widget_scaffold}
|
||||||
|
///
|
||||||
|
/// This sample shows how to display a [MyDataTable] with three columns: name, age, and
|
||||||
|
/// role. The columns are defined by three [MyDataColumn] objects. The table
|
||||||
|
/// contains three rows of data for three example users, the data for which
|
||||||
|
/// is defined by three [MyDataRow] objects.
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// return MyDataTable(
|
||||||
|
/// columns: const <MyDataColumn>[
|
||||||
|
/// MyDataColumn(
|
||||||
|
/// label: Text(
|
||||||
|
/// 'Name',
|
||||||
|
/// style: TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// MyDataColumn(
|
||||||
|
/// label: Text(
|
||||||
|
/// 'Age',
|
||||||
|
/// style: TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// MyDataColumn(
|
||||||
|
/// label: Text(
|
||||||
|
/// 'Role',
|
||||||
|
/// style: TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ],
|
||||||
|
/// rows: const <MyDataRow>[
|
||||||
|
/// MyDataRow(
|
||||||
|
/// cells: <MyDataCell>[
|
||||||
|
/// MyDataCell(Text('Sarah')),
|
||||||
|
/// MyDataCell(Text('19')),
|
||||||
|
/// MyDataCell(Text('Student')),
|
||||||
|
/// ],
|
||||||
|
/// ),
|
||||||
|
/// MyDataRow(
|
||||||
|
/// cells: <MyDataCell>[
|
||||||
|
/// MyDataCell(Text('Janine')),
|
||||||
|
/// MyDataCell(Text('43')),
|
||||||
|
/// MyDataCell(Text('Professor')),
|
||||||
|
/// ],
|
||||||
|
/// ),
|
||||||
|
/// MyDataRow(
|
||||||
|
/// cells: <MyDataCell>[
|
||||||
|
/// MyDataCell(Text('William')),
|
||||||
|
/// MyDataCell(Text('27')),
|
||||||
|
/// MyDataCell(Text('Associate Professor')),
|
||||||
|
/// ],
|
||||||
|
/// ),
|
||||||
|
/// ],
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// {@end-tool}
|
||||||
|
// TODO(ianh): Also suggest [ScrollingMyDataTable] once we have it.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [MyDataColumn], which describes a column in the data table.
|
||||||
|
/// * [MyDataRow], which contains the data for a row in the data table.
|
||||||
|
/// * [MyDataCell], which contains the data for a single cell in the data table.
|
||||||
|
/// * [PaginatedMyDataTable], which shows part of the data in a data table and
|
||||||
|
/// provides controls for paging through the remainder of the data.
|
||||||
|
/// * <https://material.io/design/components/data-tables.html>
|
||||||
|
class MyDataTable extends StatelessWidget {
|
||||||
|
/// Creates a widget describing a data table.
|
||||||
|
///
|
||||||
|
/// The [columns] argument must be a list of as many [MyDataColumn]
|
||||||
|
/// objects as the table is to have columns, ignoring the leading
|
||||||
|
/// checkbox column if any. The [columns] argument must have a
|
||||||
|
/// length greater than zero and must not be null.
|
||||||
|
///
|
||||||
|
/// The [rows] argument must be a list of as many [MyDataRow] objects
|
||||||
|
/// as the table is to have rows, ignoring the leading heading row
|
||||||
|
/// that contains the column headings (derived from the [columns]
|
||||||
|
/// argument). There may be zero rows, but the rows argument must
|
||||||
|
/// not be null.
|
||||||
|
///
|
||||||
|
/// Each [MyDataRow] object in [rows] must have as many [MyDataCell]
|
||||||
|
/// objects in the [MyDataRow.cells] list as the table has columns.
|
||||||
|
///
|
||||||
|
/// If the table is sorted, the column that provides the current
|
||||||
|
/// primary key should be specified by index in [sortColumnIndex], 0
|
||||||
|
/// meaning the first column in [columns], 1 being the next one, and
|
||||||
|
/// so forth.
|
||||||
|
///
|
||||||
|
/// The actual sort order can be specified using [sortAscending]; if
|
||||||
|
/// the sort order is ascending, this should be true (the default),
|
||||||
|
/// otherwise it should be false.
|
||||||
|
MyDataTable({
|
||||||
|
Key key,
|
||||||
|
@required this.columns,
|
||||||
|
this.sortColumnIndex,
|
||||||
|
this.sortAscending = true,
|
||||||
|
this.onSelectAll,
|
||||||
|
this.MyDataRowHeight = kMinInteractiveDimension,
|
||||||
|
this.headingRowHeight = 56.0,
|
||||||
|
this.horizontalMargin = 24.0,
|
||||||
|
this.columnSpacing = 56.0,
|
||||||
|
this.oddLine,
|
||||||
|
this.evenLine,
|
||||||
|
@required this.rows,
|
||||||
|
}) : assert(columns != null),
|
||||||
|
assert(columns.isNotEmpty),
|
||||||
|
assert(sortColumnIndex == null ||
|
||||||
|
(sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
|
||||||
|
assert(sortAscending != null),
|
||||||
|
assert(MyDataRowHeight != null),
|
||||||
|
assert(headingRowHeight != null),
|
||||||
|
assert(horizontalMargin != null),
|
||||||
|
assert(columnSpacing != null),
|
||||||
|
assert(rows != null),
|
||||||
|
assert(
|
||||||
|
!rows.any((MyDataRow row) => row.cells.length != columns.length)),
|
||||||
|
_onlyTextColumn = _initOnlyTextColumn(columns),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// The configuration and labels for the columns in the table.
|
||||||
|
final List<MyDataColumn> columns;
|
||||||
|
|
||||||
|
final Decoration oddLine;
|
||||||
|
final Decoration evenLine;
|
||||||
|
|
||||||
|
/// The current primary sort key's column.
|
||||||
|
///
|
||||||
|
/// If non-null, indicates that the indicated column is the column
|
||||||
|
/// by which the data is sorted. The number must correspond to the
|
||||||
|
/// index of the relevant column in [columns].
|
||||||
|
///
|
||||||
|
/// Setting this will cause the relevant column to have a sort
|
||||||
|
/// indicator displayed.
|
||||||
|
///
|
||||||
|
/// When this is null, it implies that the table's sort order does
|
||||||
|
/// not correspond to any of the columns.
|
||||||
|
final int sortColumnIndex;
|
||||||
|
|
||||||
|
/// Whether the column mentioned in [sortColumnIndex], if any, is sorted
|
||||||
|
/// in ascending order.
|
||||||
|
///
|
||||||
|
/// If true, the order is ascending (meaning the rows with the
|
||||||
|
/// smallest values for the current sort column are first in the
|
||||||
|
/// table).
|
||||||
|
///
|
||||||
|
/// If false, the order is descending (meaning the rows with the
|
||||||
|
/// smallest values for the current sort column are last in the
|
||||||
|
/// table).
|
||||||
|
final bool sortAscending;
|
||||||
|
|
||||||
|
/// Invoked when the user selects or unselects every row, using the
|
||||||
|
/// checkbox in the heading row.
|
||||||
|
///
|
||||||
|
/// If this is null, then the [MyDataRow.onSelectChanged] callback of
|
||||||
|
/// every row in the table is invoked appropriately instead.
|
||||||
|
///
|
||||||
|
/// To control whether a particular row is selectable or not, see
|
||||||
|
/// [MyDataRow.onSelectChanged]. This callback is only relevant if any
|
||||||
|
/// row is selectable.
|
||||||
|
final ValueSetter<bool> onSelectAll;
|
||||||
|
|
||||||
|
/// The height of each row (excluding the row that contains column headings).
|
||||||
|
///
|
||||||
|
/// This value defaults to kMinInteractiveDimension to adhere to the Material
|
||||||
|
/// Design specifications.
|
||||||
|
final double MyDataRowHeight;
|
||||||
|
|
||||||
|
/// The height of the heading row.
|
||||||
|
///
|
||||||
|
/// This value defaults to 56.0 to adhere to the Material Design specifications.
|
||||||
|
final double headingRowHeight;
|
||||||
|
|
||||||
|
/// The horizontal margin between the edges of the table and the content
|
||||||
|
/// in the first and last cells of each row.
|
||||||
|
///
|
||||||
|
/// When a checkbox is displayed, it is also the margin between the checkbox
|
||||||
|
/// the content in the first data column.
|
||||||
|
///
|
||||||
|
/// This value defaults to 24.0 to adhere to the Material Design specifications.
|
||||||
|
final double horizontalMargin;
|
||||||
|
|
||||||
|
/// The horizontal margin between the contents of each data column.
|
||||||
|
///
|
||||||
|
/// This value defaults to 56.0 to adhere to the Material Design specifications.
|
||||||
|
final double columnSpacing;
|
||||||
|
|
||||||
|
/// The data to show in each row (excluding the row that contains
|
||||||
|
/// the column headings).
|
||||||
|
///
|
||||||
|
/// Must be non-null, but may be empty.
|
||||||
|
final List<MyDataRow> rows;
|
||||||
|
|
||||||
|
// Set by the constructor to the index of the only Column that is
|
||||||
|
// non-numeric, if there is exactly one, otherwise null.
|
||||||
|
final int _onlyTextColumn;
|
||||||
|
static int _initOnlyTextColumn(List<MyDataColumn> columns) {
|
||||||
|
int result;
|
||||||
|
for (int index = 0; index < columns.length; index += 1) {
|
||||||
|
final MyDataColumn column = columns[index];
|
||||||
|
if (!column.numeric) {
|
||||||
|
if (result != null) return null;
|
||||||
|
result = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _debugInteractive {
|
||||||
|
return columns.any((MyDataColumn column) => column._debugInteractive) ||
|
||||||
|
rows.any((MyDataRow row) => row._debugInteractive);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final LocalKey _headingRowKey = UniqueKey();
|
||||||
|
|
||||||
|
void _handleSelectAll(bool checked) {
|
||||||
|
if (onSelectAll != null) {
|
||||||
|
onSelectAll(checked);
|
||||||
|
} else {
|
||||||
|
for (MyDataRow row in rows) {
|
||||||
|
if ((row.onSelectChanged != null) && (row.selected != checked))
|
||||||
|
row.onSelectChanged(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const double _sortArrowPadding = 2.0;
|
||||||
|
static const double _headingFontSize = 12.0;
|
||||||
|
static const Duration _sortArrowAnimationDuration =
|
||||||
|
Duration(milliseconds: 150);
|
||||||
|
static const Color _grey100Opacity =
|
||||||
|
Color(0x0A000000); // Grey 100 as opacity instead of solid color
|
||||||
|
static const Color _grey300Opacity =
|
||||||
|
Color(0x1E000000); // Dark theme variant is just a guess.
|
||||||
|
|
||||||
|
Widget _buildCheckbox({
|
||||||
|
Color color,
|
||||||
|
bool checked,
|
||||||
|
VoidCallback onRowTap,
|
||||||
|
ValueChanged<bool> onCheckboxChanged,
|
||||||
|
}) {
|
||||||
|
Widget contents = Semantics(
|
||||||
|
container: true,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
start: horizontalMargin, end: horizontalMargin / 2.0),
|
||||||
|
child: Center(
|
||||||
|
child: Checkbox(
|
||||||
|
activeColor: color,
|
||||||
|
value: checked,
|
||||||
|
onChanged: onCheckboxChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (onRowTap != null) {
|
||||||
|
contents = TableRowInkWell(
|
||||||
|
onTap: onRowTap,
|
||||||
|
child: contents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return TableCell(
|
||||||
|
verticalAlignment: TableCellVerticalAlignment.fill,
|
||||||
|
child: contents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeadingCell({
|
||||||
|
BuildContext context,
|
||||||
|
EdgeInsetsGeometry padding,
|
||||||
|
Widget label,
|
||||||
|
String tooltip,
|
||||||
|
bool numeric,
|
||||||
|
VoidCallback onSort,
|
||||||
|
bool sorted,
|
||||||
|
bool ascending,
|
||||||
|
}) {
|
||||||
|
if (onSort != null) {
|
||||||
|
final Widget arrow = _SortArrow(
|
||||||
|
visible: sorted,
|
||||||
|
down: sorted ? ascending : null,
|
||||||
|
duration: _sortArrowAnimationDuration,
|
||||||
|
);
|
||||||
|
const Widget arrowPadding = SizedBox(width: _sortArrowPadding);
|
||||||
|
label = Row(
|
||||||
|
textDirection: numeric ? TextDirection.rtl : null,
|
||||||
|
children: <Widget>[label, arrowPadding, arrow],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
label = Container(
|
||||||
|
padding: padding,
|
||||||
|
height: headingRowHeight,
|
||||||
|
alignment:
|
||||||
|
numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
style: TextStyle(
|
||||||
|
// TODO(ianh): font family should match Theme; see https://github.com/flutter/flutter/issues/3116
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: _headingFontSize,
|
||||||
|
height: math.min(1.0, headingRowHeight / _headingFontSize),
|
||||||
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
|
? ((onSort != null && sorted) ? Colors.black87 : Colors.black54)
|
||||||
|
: ((onSort != null && sorted) ? Colors.white : Colors.white70),
|
||||||
|
),
|
||||||
|
softWrap: false,
|
||||||
|
duration: _sortArrowAnimationDuration,
|
||||||
|
child: label,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (tooltip != null) {
|
||||||
|
label = Tooltip(
|
||||||
|
message: tooltip,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (onSort != null) {
|
||||||
|
label = InkWell(
|
||||||
|
onTap: onSort,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMyDataCell({
|
||||||
|
BuildContext context,
|
||||||
|
EdgeInsetsGeometry padding,
|
||||||
|
Widget label,
|
||||||
|
bool numeric,
|
||||||
|
bool placeholder,
|
||||||
|
bool showEditIcon,
|
||||||
|
VoidCallback onTap,
|
||||||
|
VoidCallback onSelectChanged,
|
||||||
|
}) {
|
||||||
|
final bool isLightTheme = Theme.of(context).brightness == Brightness.light;
|
||||||
|
if (showEditIcon) {
|
||||||
|
const Widget icon = Icon(Icons.edit, size: 18.0);
|
||||||
|
label = Expanded(child: label);
|
||||||
|
label = Row(
|
||||||
|
textDirection: numeric ? TextDirection.rtl : null,
|
||||||
|
children: <Widget>[label, icon],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
label = Container(
|
||||||
|
padding: padding,
|
||||||
|
height: MyDataRowHeight,
|
||||||
|
alignment:
|
||||||
|
numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: TextStyle(
|
||||||
|
// TODO(ianh): font family should be Roboto; see https://github.com/flutter/flutter/issues/3116
|
||||||
|
fontSize: 13.0,
|
||||||
|
color: isLightTheme
|
||||||
|
? (placeholder ? Colors.black38 : Colors.black87)
|
||||||
|
: (placeholder ? Colors.white38 : Colors.white70),
|
||||||
|
),
|
||||||
|
child: IconTheme.merge(
|
||||||
|
data: IconThemeData(
|
||||||
|
color: isLightTheme ? Colors.black54 : Colors.white70,
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(child: label),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (onTap != null) {
|
||||||
|
label = InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
} else if (onSelectChanged != null) {
|
||||||
|
label = TableRowInkWell(
|
||||||
|
onTap: onSelectChanged,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(!_debugInteractive || debugCheckHasMaterial(context));
|
||||||
|
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final BoxDecoration _kSelectedDecoration = BoxDecoration(
|
||||||
|
border: Border(bottom: Divider.createBorderSide(context, width: 1.0)),
|
||||||
|
// The backgroundColor has to be transparent so you can see the ink on the material
|
||||||
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
|
? _grey100Opacity
|
||||||
|
: _grey300Opacity,
|
||||||
|
);
|
||||||
|
final BoxDecoration _kUnselectedDecoration = BoxDecoration(
|
||||||
|
border: Border(bottom: Divider.createBorderSide(context, width: 1.0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final bool showCheckboxColumn = false;
|
||||||
|
final bool allChecked = false;
|
||||||
|
|
||||||
|
final List<TableColumnWidth> tableColumns =
|
||||||
|
List<TableColumnWidth>(columns.length + (showCheckboxColumn ? 1 : 0));
|
||||||
|
final List<TableRow> tableRows = List<TableRow>.generate(
|
||||||
|
rows.length + 1, // the +1 is for the header row
|
||||||
|
(int index) {
|
||||||
|
return TableRow(
|
||||||
|
key: index == 0 ? _headingRowKey : rows[index - 1].key,
|
||||||
|
decoration: index > 0 && rows[index - 1].selected
|
||||||
|
? _kSelectedDecoration
|
||||||
|
: index > 0 && index.isOdd && oddLine != null
|
||||||
|
? oddLine
|
||||||
|
: index.isEven && evenLine != null
|
||||||
|
? evenLine
|
||||||
|
: _kUnselectedDecoration,
|
||||||
|
children: List<Widget>(tableColumns.length),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
int rowIndex;
|
||||||
|
|
||||||
|
int displayColumnIndex = 0;
|
||||||
|
if (showCheckboxColumn) {
|
||||||
|
tableColumns[0] = FixedColumnWidth(
|
||||||
|
horizontalMargin + Checkbox.width + horizontalMargin / 2.0);
|
||||||
|
tableRows[0].children[0] = _buildCheckbox(
|
||||||
|
color: theme.accentColor,
|
||||||
|
checked: allChecked,
|
||||||
|
onCheckboxChanged: _handleSelectAll,
|
||||||
|
);
|
||||||
|
rowIndex = 1;
|
||||||
|
for (MyDataRow row in rows) {
|
||||||
|
tableRows[rowIndex].children[0] = _buildCheckbox(
|
||||||
|
color: theme.accentColor,
|
||||||
|
checked: row.selected,
|
||||||
|
onRowTap: () => row.onSelectChanged != null
|
||||||
|
? row.onSelectChanged(!row.selected)
|
||||||
|
: null,
|
||||||
|
onCheckboxChanged: row.onSelectChanged,
|
||||||
|
);
|
||||||
|
rowIndex += 1;
|
||||||
|
}
|
||||||
|
displayColumnIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int MyDataColumnIndex = 0;
|
||||||
|
MyDataColumnIndex < columns.length;
|
||||||
|
MyDataColumnIndex += 1) {
|
||||||
|
final MyDataColumn column = columns[MyDataColumnIndex];
|
||||||
|
|
||||||
|
double paddingStart;
|
||||||
|
if (MyDataColumnIndex == 0 && showCheckboxColumn) {
|
||||||
|
paddingStart = horizontalMargin / 2.0;
|
||||||
|
} else if (MyDataColumnIndex == 0 && !showCheckboxColumn) {
|
||||||
|
paddingStart = horizontalMargin;
|
||||||
|
} else {
|
||||||
|
paddingStart = columnSpacing / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double paddingEnd;
|
||||||
|
if (MyDataColumnIndex == columns.length - 1) {
|
||||||
|
paddingEnd = horizontalMargin;
|
||||||
|
} else {
|
||||||
|
paddingEnd = columnSpacing / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
|
||||||
|
start: paddingStart,
|
||||||
|
end: paddingEnd,
|
||||||
|
);
|
||||||
|
if (MyDataColumnIndex == _onlyTextColumn) {
|
||||||
|
tableColumns[displayColumnIndex] =
|
||||||
|
const IntrinsicColumnWidth(flex: 1.0);
|
||||||
|
} else {
|
||||||
|
tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
|
||||||
|
}
|
||||||
|
tableRows[0].children[displayColumnIndex] = _buildHeadingCell(
|
||||||
|
context: context,
|
||||||
|
padding: padding,
|
||||||
|
label: column.label,
|
||||||
|
tooltip: column.tooltip,
|
||||||
|
numeric: column.numeric,
|
||||||
|
onSort: () => column.onSort != null
|
||||||
|
? column.onSort(MyDataColumnIndex,
|
||||||
|
sortColumnIndex != MyDataColumnIndex || !sortAscending)
|
||||||
|
: null,
|
||||||
|
sorted: MyDataColumnIndex == sortColumnIndex,
|
||||||
|
ascending: sortAscending,
|
||||||
|
);
|
||||||
|
rowIndex = 1;
|
||||||
|
for (MyDataRow row in rows) {
|
||||||
|
final MyDataCell cell = row.cells[MyDataColumnIndex];
|
||||||
|
tableRows[rowIndex].children[displayColumnIndex] = _buildMyDataCell(
|
||||||
|
context: context,
|
||||||
|
padding: padding,
|
||||||
|
label: cell.child,
|
||||||
|
numeric: column.numeric,
|
||||||
|
placeholder: cell.placeholder,
|
||||||
|
showEditIcon: cell.showEditIcon,
|
||||||
|
onTap: cell.onTap,
|
||||||
|
onSelectChanged: () => row.onSelectChanged != null
|
||||||
|
? row.onSelectChanged(!row.selected)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
rowIndex += 1;
|
||||||
|
}
|
||||||
|
displayColumnIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Table(
|
||||||
|
columnWidths: tableColumns.asMap(),
|
||||||
|
children: tableRows,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rectangular area of a Material that responds to touch but clips
|
||||||
|
/// its ink splashes to the current table row of the nearest table.
|
||||||
|
///
|
||||||
|
/// Must have an ancestor [Material] widget in which to cause ink
|
||||||
|
/// reactions and an ancestor [Table] widget to establish a row.
|
||||||
|
///
|
||||||
|
/// The [TableRowInkWell] must be in the same coordinate space (modulo
|
||||||
|
/// translations) as the [Table]. If it's rotated or scaled or
|
||||||
|
/// otherwise transformed, it will not be able to describe the
|
||||||
|
/// rectangle of the row in its own coordinate system as a [Rect], and
|
||||||
|
/// thus the splash will not occur. (In general, this is easy to
|
||||||
|
/// achieve: just put the [TableRowInkWell] as the direct child of the
|
||||||
|
/// [Table], and put the other contents of the cell inside it.)
|
||||||
|
class TableRowInkWell extends InkResponse {
|
||||||
|
/// Creates an ink well for a table row.
|
||||||
|
const TableRowInkWell({
|
||||||
|
Key key,
|
||||||
|
Widget child,
|
||||||
|
GestureTapCallback onTap,
|
||||||
|
GestureTapCallback onDoubleTap,
|
||||||
|
GestureLongPressCallback onLongPress,
|
||||||
|
ValueChanged<bool> onHighlightChanged,
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
child: child,
|
||||||
|
onTap: onTap,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHighlightChanged: onHighlightChanged,
|
||||||
|
containedInkWell: true,
|
||||||
|
highlightShape: BoxShape.rectangle,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
RectCallback getRectCallback(RenderBox referenceBox) {
|
||||||
|
return () {
|
||||||
|
RenderObject cell = referenceBox;
|
||||||
|
AbstractNode table = cell.parent;
|
||||||
|
final Matrix4 transform = Matrix4.identity();
|
||||||
|
while (table is RenderObject && table is! RenderTable) {
|
||||||
|
final RenderObject parentBox = table as RenderObject;
|
||||||
|
parentBox.applyPaintTransform(cell, transform);
|
||||||
|
assert(table == cell.parent);
|
||||||
|
cell = parentBox;
|
||||||
|
table = table.parent;
|
||||||
|
}
|
||||||
|
if (table is RenderTable) {
|
||||||
|
final TableCellParentData cellParentData =
|
||||||
|
cell.parentData as TableCellParentData;
|
||||||
|
assert(cellParentData.y != null);
|
||||||
|
final Rect rect = table.getRowBox(cellParentData.y);
|
||||||
|
// The rect is in the table's coordinate space. We need to change it to the
|
||||||
|
// TableRowInkWell's coordinate space.
|
||||||
|
table.applyPaintTransform(cell, transform);
|
||||||
|
final Offset offset = MatrixUtils.getAsTranslation(transform);
|
||||||
|
if (offset != null) return rect.shift(-offset);
|
||||||
|
}
|
||||||
|
return Rect.zero;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool debugCheckContext(BuildContext context) {
|
||||||
|
assert(debugCheckHasTable(context));
|
||||||
|
return super.debugCheckContext(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SortArrow extends StatefulWidget {
|
||||||
|
const _SortArrow({
|
||||||
|
Key key,
|
||||||
|
this.visible,
|
||||||
|
this.down,
|
||||||
|
this.duration,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final bool visible;
|
||||||
|
|
||||||
|
final bool down;
|
||||||
|
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SortArrowState createState() => _SortArrowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
|
||||||
|
AnimationController _opacityController;
|
||||||
|
Animation<double> _opacityAnimation;
|
||||||
|
|
||||||
|
AnimationController _orientationController;
|
||||||
|
Animation<double> _orientationAnimation;
|
||||||
|
double _orientationOffset = 0.0;
|
||||||
|
|
||||||
|
bool _down;
|
||||||
|
|
||||||
|
static final Animatable<double> _turnTween =
|
||||||
|
Tween<double>(begin: 0.0, end: math.pi)
|
||||||
|
.chain(CurveTween(curve: Curves.easeIn));
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_opacityAnimation = CurvedAnimation(
|
||||||
|
parent: _opacityController = AnimationController(
|
||||||
|
duration: widget.duration,
|
||||||
|
vsync: this,
|
||||||
|
),
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
)..addListener(_rebuild);
|
||||||
|
_opacityController.value = widget.visible ? 1.0 : 0.0;
|
||||||
|
_orientationController = AnimationController(
|
||||||
|
duration: widget.duration,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_orientationAnimation = _orientationController.drive(_turnTween)
|
||||||
|
..addListener(_rebuild)
|
||||||
|
..addStatusListener(_resetOrientationAnimation);
|
||||||
|
if (widget.visible) _orientationOffset = widget.down ? 0.0 : math.pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _rebuild() {
|
||||||
|
setState(() {
|
||||||
|
// The animations changed, so we need to rebuild.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetOrientationAnimation(AnimationStatus status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
assert(_orientationAnimation.value == math.pi);
|
||||||
|
_orientationOffset += math.pi;
|
||||||
|
_orientationController.value =
|
||||||
|
0.0; // TODO(ianh): This triggers a pointless rebuild.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(_SortArrow oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
bool skipArrow = false;
|
||||||
|
final bool newDown = widget.down ?? _down;
|
||||||
|
if (oldWidget.visible != widget.visible) {
|
||||||
|
if (widget.visible &&
|
||||||
|
(_opacityController.status == AnimationStatus.dismissed)) {
|
||||||
|
_orientationController.stop();
|
||||||
|
_orientationController.value = 0.0;
|
||||||
|
_orientationOffset = newDown ? 0.0 : math.pi;
|
||||||
|
skipArrow = true;
|
||||||
|
}
|
||||||
|
if (widget.visible) {
|
||||||
|
_opacityController.forward();
|
||||||
|
} else {
|
||||||
|
_opacityController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((_down != newDown) && !skipArrow) {
|
||||||
|
if (_orientationController.status == AnimationStatus.dismissed) {
|
||||||
|
_orientationController.forward();
|
||||||
|
} else {
|
||||||
|
_orientationController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_down = newDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_opacityController.dispose();
|
||||||
|
_orientationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const double _arrowIconBaselineOffset = -1.5;
|
||||||
|
static const double _arrowIconSize = 16.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: _opacityAnimation.value,
|
||||||
|
child: Transform(
|
||||||
|
transform:
|
||||||
|
Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value)
|
||||||
|
..setTranslationRaw(0.0, _arrowIconBaselineOffset, 0.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Icon(
|
||||||
|
Icons.arrow_downward,
|
||||||
|
size: _arrowIconSize,
|
||||||
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
|
? Colors.black87
|
||||||
|
: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:fcs/fcs/common/data/providers/auth_fb.dart';
|
import 'package:fcs/fcs/common/data/providers/auth_fb.dart';
|
||||||
import 'package:fcs/fcs/common/data/providers/user_data_provider.dart';
|
import 'package:fcs/fcs/common/data/providers/user_data_provider.dart';
|
||||||
import 'package:fcs/fcs/common/data/providers/user_local_data_provider.dart';
|
|
||||||
import 'package:fcs/fcs/common/domain/entities/auth_result.dart';
|
import 'package:fcs/fcs/common/domain/entities/auth_result.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/connectivity.dart';
|
import 'package:fcs/fcs/common/domain/entities/connectivity.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/setting.dart';
|
import 'package:fcs/fcs/common/domain/entities/setting.dart';
|
||||||
@@ -13,11 +12,9 @@ class AuthServiceImp implements AuthService {
|
|||||||
AuthServiceImp({
|
AuthServiceImp({
|
||||||
@required this.authFb,
|
@required this.authFb,
|
||||||
@required this.connectivity,
|
@required this.connectivity,
|
||||||
@required this.userLocalDataProvider,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Connectivity connectivity;
|
final Connectivity connectivity;
|
||||||
final UserLocalDataProvider userLocalDataProvider;
|
|
||||||
final AuthFb authFb;
|
final AuthFb authFb;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class Services {
|
|||||||
_authService = AuthServiceImp(
|
_authService = AuthServiceImp(
|
||||||
authFb: AuthFb.instance,
|
authFb: AuthFb.instance,
|
||||||
connectivity: null,
|
connectivity: null,
|
||||||
userLocalDataProvider: null);
|
);
|
||||||
_userService = UserServiceImp(
|
_userService = UserServiceImp(
|
||||||
connectivity: null, userDataProvider: UserDataProvider());
|
connectivity: null, userDataProvider: UserDataProvider());
|
||||||
_messagingService = MessagingServiceImp();
|
_messagingService = MessagingServiceImp();
|
||||||
|
|||||||
@@ -33,4 +33,9 @@ class UserServiceImp implements UserService {
|
|||||||
Future<User> findUser(String phoneNumber) {
|
Future<User> findUser(String phoneNumber) {
|
||||||
return userDataProvider.findUser(phoneNumber);
|
return userDataProvider.findUser(phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<User>> searchUser(String term) {
|
||||||
|
return userDataProvider.searchUser(term);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ abstract class UserService {
|
|||||||
Future<void> deleteInvite(String phoneNumber);
|
Future<void> deleteInvite(String phoneNumber);
|
||||||
Future<void> acceptRequest(String userID);
|
Future<void> acceptRequest(String userID);
|
||||||
Future<User> findUser(String phoneNumber);
|
Future<User> findUser(String phoneNumber);
|
||||||
|
Future<List<User>> searchUser(String term);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ void main() {
|
|||||||
Config(
|
Config(
|
||||||
flavor: Flavor.DEV,
|
flavor: Flavor.DEV,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
|
reportURL: "http://192.168.100.11:8080",
|
||||||
|
reportProjectID: "fcs-dev",
|
||||||
apiURL: "http://192.168.100.11:7777",
|
apiURL: "http://192.168.100.11:7777",
|
||||||
level: Level.ALL);
|
level: Level.ALL);
|
||||||
runApp(App());
|
runApp(App());
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:barcode_scan/barcode_scan.dart';
|
import 'package:barcode_scan/barcode_scan.dart';
|
||||||
import 'package:barcode_scan/model/scan_result.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_icons/flutter_icons.dart';
|
import 'package:flutter_icons/flutter_icons.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
@@ -20,7 +19,7 @@ class BarcodeScreenPage extends StatefulWidget {
|
|||||||
class _BarcodeScreenPageState extends State<BarcodeScreenPage> {
|
class _BarcodeScreenPageState extends State<BarcodeScreenPage> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
ScanResult scanResult;
|
String scanResult;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|||||||
16
pubspec.lock
16
pubspec.lock
@@ -28,7 +28,7 @@ packages:
|
|||||||
name: barcode_scan
|
name: barcode_scan
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "2.0.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -295,13 +295,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.6"
|
version: "3.1.6"
|
||||||
fixnum:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: fixnum
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.10.11"
|
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -681,13 +674,6 @@ packages:
|
|||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
protobuf:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: protobuf
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ dependencies:
|
|||||||
flutter_icons: ^1.1.0
|
flutter_icons: ^1.1.0
|
||||||
country_icons: ^1.1.1
|
country_icons: ^1.1.1
|
||||||
timeline_list: ^0.0.5
|
timeline_list: ^0.0.5
|
||||||
barcode_scan: ^3.0.1
|
barcode_scan: ^2.0.2
|
||||||
flutter_pdfview: ^1.0.3
|
flutter_pdfview: ^1.0.3
|
||||||
flutter_local_notifications: ^1.4.4+4
|
flutter_local_notifications: ^1.4.4+4
|
||||||
share: ^0.6.5
|
share: ^0.6.5
|
||||||
|
|||||||
Reference in New Issue
Block a user