add structure
This commit is contained in:
163
lib/app.dart
Normal file
163
lib/app.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/buyer_model.dart';
|
||||
import 'package:fcs/model/delivery_model.dart';
|
||||
import 'package:fcs/model/manual_model.dart';
|
||||
import 'package:fcs/model/notification_model.dart';
|
||||
import 'package:fcs/model/pd_model.dart';
|
||||
import 'package:fcs/model/reg_model.dart';
|
||||
import 'package:fcs/model/report_model.dart';
|
||||
import 'package:fcs/model/storage_model.dart';
|
||||
import 'package:fcs/model/test_model.dart';
|
||||
import 'package:fcs/pages/email_page.dart';
|
||||
import 'package:fcs/pages/login_page.dart';
|
||||
|
||||
import 'model/announcement_model.dart';
|
||||
import 'model/chart_model.dart';
|
||||
import 'model/device_model.dart';
|
||||
import 'model/do_model.dart';
|
||||
import 'model/employee_model.dart';
|
||||
import 'model/language_model.dart';
|
||||
import 'model/log_model.dart';
|
||||
import 'model/main_model.dart';
|
||||
import 'model/po_model.dart';
|
||||
import 'model/product_model.dart';
|
||||
import 'model/report_user_model.dart';
|
||||
import 'model/user_model.dart';
|
||||
import 'pages/home_page.dart';
|
||||
import 'pages/splash.dart';
|
||||
import 'pages/term.dart';
|
||||
import 'pages/welcome_page.dart';
|
||||
import 'widget/localization/app_translations_delegate.dart';
|
||||
import 'widget/localization/transalation.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
@override
|
||||
_AppState createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> {
|
||||
final MainModel mainModel = new MainModel();
|
||||
final UserModel userModel = new UserModel();
|
||||
final ProductModel productModel = new ProductModel();
|
||||
final EmployeeModel employeeModel = new EmployeeModel();
|
||||
final POSubmissionModel poSubmissionModel = new POSubmissionModel();
|
||||
final DOModel doModel = new DOModel();
|
||||
final LanguageModel lanuguageModel = new LanguageModel();
|
||||
final StorageModel storageModel = new StorageModel();
|
||||
final PDModel pdModel = new PDModel();
|
||||
final RegModel regModel = new RegModel();
|
||||
final BuyerModel buyerModel = new BuyerModel();
|
||||
final NotificationModel notificationModel = new NotificationModel();
|
||||
final ChartModel chartModel = new ChartModel();
|
||||
final DeliveryModel deliveryModel = new DeliveryModel();
|
||||
final ManualModel manualModel = new ManualModel();
|
||||
final TestModel testModel = new TestModel();
|
||||
final LogModel logModel = new LogModel();
|
||||
final PhoneDeviceModel phoneDeviceModel = new PhoneDeviceModel();
|
||||
final ReportModel reportModel = new ReportModel();
|
||||
final AnnouncementModel announcementModel = new AnnouncementModel();
|
||||
final ReportUserModel reportUserModel = new ReportUserModel();
|
||||
|
||||
AppTranslationsDelegate _newLocaleDelegate;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_newLocaleDelegate = AppTranslationsDelegate(newLocale: null);
|
||||
Translation().onLocaleChanged = onLocaleChange;
|
||||
mainModel
|
||||
..addModel(userModel)
|
||||
..addModel(employeeModel)
|
||||
..addModel(lanuguageModel)
|
||||
..addModel(storageModel)
|
||||
..addModel(regModel)
|
||||
..addModel(poSubmissionModel)
|
||||
..addModel(doModel)
|
||||
..addModel(productModel)
|
||||
..addModel(pdModel)
|
||||
..addModel(buyerModel)
|
||||
..addModel(notificationModel)
|
||||
..addModel(chartModel)
|
||||
..addModel(deliveryModel)
|
||||
..addModel(logModel)
|
||||
..addModel(manualModel)
|
||||
..addModel(phoneDeviceModel)
|
||||
..addModel(regModel)
|
||||
..addModel(announcementModel)
|
||||
..addModel(reportModel)
|
||||
..addModel(testModel)
|
||||
..addModel(reportUserModel);
|
||||
this.mainModel.init();
|
||||
}
|
||||
|
||||
void onLocaleChange(Locale locale) {
|
||||
setState(() {
|
||||
_newLocaleDelegate = AppTranslationsDelegate(newLocale: locale);
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, WidgetBuilder> route(BuildContext context) {
|
||||
final routes = <String, WidgetBuilder>{
|
||||
'/': (_) => SplashScreen(),
|
||||
'/home': (_) => HomePage(),
|
||||
'/welcome': (context) => WelcomePage(),
|
||||
'/term': (context) => Term(
|
||||
agreePage: true,
|
||||
),
|
||||
'/login': (context) => LoginPage(),
|
||||
'/email': (context) => EmailPage()
|
||||
};
|
||||
return routes;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(builder: (context) => mainModel),
|
||||
ChangeNotifierProvider(builder: (context) => userModel),
|
||||
ChangeNotifierProvider(builder: (context) => productModel),
|
||||
ChangeNotifierProvider(builder: (context) => employeeModel),
|
||||
ChangeNotifierProvider(builder: (context) => poSubmissionModel),
|
||||
ChangeNotifierProvider(builder: (context) => doModel),
|
||||
ChangeNotifierProvider(builder: (context) => storageModel),
|
||||
ChangeNotifierProvider(builder: (context) => pdModel),
|
||||
ChangeNotifierProvider(builder: (context) => lanuguageModel),
|
||||
ChangeNotifierProvider(builder: (context) => regModel),
|
||||
ChangeNotifierProvider(builder: (context) => buyerModel),
|
||||
ChangeNotifierProvider(builder: (context) => notificationModel),
|
||||
ChangeNotifierProvider(builder: (context) => chartModel),
|
||||
ChangeNotifierProvider(builder: (context) => deliveryModel),
|
||||
ChangeNotifierProvider(builder: (context) => manualModel),
|
||||
ChangeNotifierProvider(builder: (context) => logModel),
|
||||
ChangeNotifierProvider(builder: (context) => deliveryModel),
|
||||
ChangeNotifierProvider(builder: (context) => phoneDeviceModel),
|
||||
ChangeNotifierProvider(builder: (context) => reportModel),
|
||||
ChangeNotifierProvider(builder: (context) => announcementModel),
|
||||
ChangeNotifierProvider(builder: (context) => reportUserModel),
|
||||
ChangeNotifierProvider(
|
||||
builder: (context) => testModel,
|
||||
),
|
||||
],
|
||||
child: Consumer<LanguageModel>(
|
||||
builder: (BuildContext context, LanguageModel value, Widget child) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Ok Energy',
|
||||
routes: route(context),
|
||||
theme: ThemeData(accentColor: Colors.black),
|
||||
localizationsDelegates: [
|
||||
_newLocaleDelegate,
|
||||
//provides localised strings
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
//provides RTL support
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: Translation().supportedLocales());
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
119
lib/charts/bar_chart.dart
Normal file
119
lib/charts/bar_chart.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:charts_flutter/flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/charts/qtyby_customer_table.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
class BarChart extends StatefulWidget {
|
||||
@override
|
||||
_BarChartState createState() => _BarChartState();
|
||||
}
|
||||
|
||||
class _BarChartState extends State<BarChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
List<POChartData> chartSummary = new List();
|
||||
List<charts.Series<POChartData, String>> series;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var chartModel = Provider.of<ChartModel>(context, listen: false);
|
||||
this.chartSummary = chartModel.chartSummary;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var productModel = Provider.of<ProductModel>(context);
|
||||
if (this.chartSummary.isNotEmpty) {
|
||||
this.chartSummary.forEach((s) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == s.productID) {
|
||||
s.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.chartSummary
|
||||
.sort((s1, s2) => s1.displayOrder.compareTo(s2.displayOrder));
|
||||
}
|
||||
List<charts.Series<POChartData, String>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: this.chartSummary,
|
||||
domainFn: (POChartData series, _) => series.productName,
|
||||
measureFn: (POChartData series, _) => series.balanceQty,
|
||||
colorFn: (POChartData series, _) =>
|
||||
charts.ColorUtil.fromDartColor(series.getColor),
|
||||
labelAccessorFn: (POChartData series, _) =>
|
||||
'${numberFormatter.format(series.balanceQty)}'),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, 'product.balance_qty',
|
||||
color: primaryColor, fontSize: 16),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.refresh,
|
||||
color: primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
_load();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.BarChart(
|
||||
series,
|
||||
animate: true,
|
||||
vertical: false,
|
||||
defaultRenderer: new charts.BarRendererConfig(
|
||||
barRendererDecorator: new charts.BarLabelDecorator<String>(
|
||||
labelPosition: charts.BarLabelPosition.auto,
|
||||
),
|
||||
),
|
||||
selectionModels: [
|
||||
SelectionModelConfig(changedListener: (SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
if (selectedDatum.isNotEmpty) {
|
||||
selectedDatum.forEach((charts.SeriesDatum datumPair) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => QtyByCustomerTable(
|
||||
poChartData: datumPair.datum,
|
||||
)),
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
var _s = await chartModel.loadSummary();
|
||||
setState(() {
|
||||
this.chartSummary = _s ?? [];
|
||||
});
|
||||
}
|
||||
}
|
||||
83
lib/charts/delivery_do_line.dart
Normal file
83
lib/charts/delivery_do_line.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
import 'delivery_do_line_detail.dart';
|
||||
|
||||
class DODeliveryLineChart extends StatefulWidget {
|
||||
@override
|
||||
_DODeliveryLineChartState createState() => _DODeliveryLineChartState();
|
||||
}
|
||||
|
||||
class _DODeliveryLineChartState extends State<DODeliveryLineChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
List<charts.Series<Data, DateTime>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: chartModel.revenue.getDeliveryDo(),
|
||||
domainFn: (Data series, _) => series.date,
|
||||
measureFn: (Data series, _) => series.count,
|
||||
colorFn: (_, __) => charts.ColorUtil.fromDartColor(primaryColor),
|
||||
labelAccessorFn: (Data series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, "delivery.do.title", color: primaryColor, fontSize: 16),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"delivery.do.details",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => DODeliveryLineDetail()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.TimeSeriesChart(
|
||||
series,
|
||||
animate: true,
|
||||
defaultRenderer: new charts.LineRendererConfig(
|
||||
includePoints: true,
|
||||
),
|
||||
primaryMeasureAxis: new charts.NumericAxisSpec(
|
||||
tickProviderSpec: new charts.BasicNumericTickProviderSpec(
|
||||
zeroBound: false, desiredTickCount: 10),
|
||||
renderSpec: new charts.GridlineRendererSpec(
|
||||
lineStyle: charts.LineStyleSpec(
|
||||
dashPattern: [4, 4],
|
||||
))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
85
lib/charts/delivery_do_line_detail.dart
Normal file
85
lib/charts/delivery_do_line_detail.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DODeliveryLineDetail extends StatefulWidget {
|
||||
const DODeliveryLineDetail();
|
||||
@override
|
||||
_DODeliveryLineDetailState createState() => _DODeliveryLineDetailState();
|
||||
}
|
||||
|
||||
class _DODeliveryLineDetailState extends State<DODeliveryLineDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'delivery.do.counts',
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "delivery.date")),
|
||||
MyDataColumn(label: LocalText(context, "delivery.do.count")),
|
||||
],
|
||||
rows: getProductRow(chartModel.revenue.getDeliveryDo()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<Data> doList) {
|
||||
return doList.map((d) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(dateFormatter.format(d.date), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
numberFormatter.format(d.count),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
79
lib/charts/delivery_do_summary.dart
Normal file
79
lib/charts/delivery_do_summary.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
import 'delivery_do_summary_details.dart';
|
||||
|
||||
class DeliveryDoSummaryChart extends StatefulWidget {
|
||||
@override
|
||||
_DeliveryDoSummaryChartState createState() => _DeliveryDoSummaryChartState();
|
||||
}
|
||||
|
||||
class _DeliveryDoSummaryChartState extends State<DeliveryDoSummaryChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
List<charts.Series<Data, String>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: chartModel.revenue.getDeliveryDoSummary(),
|
||||
domainFn: (Data series, _) => "${series.totalDay}days",
|
||||
measureFn: (Data series, _) => series.totalCount,
|
||||
labelAccessorFn: (Data series, _) =>
|
||||
'${numberFormatter.format(series.totalCount)}'),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, "delivery.do.summary",
|
||||
color: primaryColor, fontSize: 16),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"delivery.do.details",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (_) => DeliveryDoSummaryDetail()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.BarChart(
|
||||
series,
|
||||
animate: true,
|
||||
vertical: false,
|
||||
defaultRenderer: new charts.BarRendererConfig(
|
||||
barRendererDecorator: new charts.BarLabelDecorator<String>(
|
||||
labelPosition: charts.BarLabelPosition.auto,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
86
lib/charts/delivery_do_summary_details.dart
Normal file
86
lib/charts/delivery_do_summary_details.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DeliveryDoSummaryDetail extends StatefulWidget {
|
||||
const DeliveryDoSummaryDetail();
|
||||
@override
|
||||
_DeliveryDoSummaryDetailState createState() =>
|
||||
_DeliveryDoSummaryDetailState();
|
||||
}
|
||||
|
||||
class _DeliveryDoSummaryDetailState extends State<DeliveryDoSummaryDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'delivery.do.sum.counts',
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "delivery.days")),
|
||||
MyDataColumn(label: LocalText(context, "delivery.do.count")),
|
||||
],
|
||||
rows: getProductRow(chartModel.revenue.getDeliveryDoSummary()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<Data> doList) {
|
||||
return doList.map((d) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(d.totalDay.toString(), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
numberFormatter.format(d.totalCount),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
107
lib/charts/delivery_line.dart
Normal file
107
lib/charts/delivery_line.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
import 'delivery_line_data.dart';
|
||||
|
||||
class DeliveryBarChart extends StatefulWidget {
|
||||
@override
|
||||
_DeliveryBarChartState createState() => _DeliveryBarChartState();
|
||||
}
|
||||
|
||||
class _DeliveryBarChartState extends State<DeliveryBarChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
List<charts.Series<Data, String>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: chartModel.revenue.getDelivery(),
|
||||
domainFn: (Data series, _) =>
|
||||
"${series.date.day}-${series.date.month}-${series.date.year}",
|
||||
measureFn: (Data series, _) => series.amount,
|
||||
labelAccessorFn: (Data series, _) =>
|
||||
'${numberFormatter.format(series.amount)}'),
|
||||
];
|
||||
|
||||
List<charts.Series<Data, DateTime>> seriesLine = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: chartModel.revenue.getDelivery(),
|
||||
domainFn: (Data series, _) => series.date,
|
||||
measureFn: (Data series, _) => series.amount,
|
||||
colorFn: (_, __) => charts.ColorUtil.fromDartColor(primaryColor),
|
||||
labelAccessorFn: (Data series, _) =>
|
||||
'${numberFormatter.format(series.amount)}',
|
||||
),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, "delivery", color: primaryColor, fontSize: 16),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"delivery.detail",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => DeliveryBarData()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.TimeSeriesChart(
|
||||
seriesLine,
|
||||
animate: true,
|
||||
defaultRenderer: new charts.LineRendererConfig(
|
||||
includePoints: true,
|
||||
),
|
||||
primaryMeasureAxis: new charts.NumericAxisSpec(
|
||||
tickProviderSpec: new charts.BasicNumericTickProviderSpec(
|
||||
zeroBound: false, desiredTickCount: 10),
|
||||
renderSpec: new charts.GridlineRendererSpec(
|
||||
lineStyle: charts.LineStyleSpec(
|
||||
dashPattern: [4, 4],
|
||||
))),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: charts.BarChart(
|
||||
// series,
|
||||
// animate: true,
|
||||
// vertical: true,
|
||||
// defaultRenderer: new charts.BarRendererConfig(
|
||||
// barRendererDecorator: new charts.BarLabelDecorator<String>(
|
||||
// labelPosition: charts.BarLabelPosition.auto,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
92
lib/charts/delivery_line_data.dart
Normal file
92
lib/charts/delivery_line_data.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'delivery_line_detail.dart';
|
||||
|
||||
class DeliveryBarData extends StatefulWidget {
|
||||
const DeliveryBarData();
|
||||
@override
|
||||
_DeliveryBarDataState createState() => _DeliveryBarDataState();
|
||||
}
|
||||
|
||||
class _DeliveryBarDataState extends State<DeliveryBarData> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'delivery.data.title',
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "delivery.date")),
|
||||
MyDataColumn(label: LocalText(context, "delivery.qty"),numeric: true),
|
||||
],
|
||||
rows: getProductRow(chartModel.revenue.getDelivery()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<Data> doList) {
|
||||
return doList.map((d) {
|
||||
var r = MyDataRow(
|
||||
onSelectChanged: (bool selected) async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DeliveryBarDetail(d.date)),
|
||||
);
|
||||
},
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(dateFormatter.format(d.date), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.amount)
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
94
lib/charts/delivery_line_detail.dart
Normal file
94
lib/charts/delivery_line_detail.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/do_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DeliveryBarDetail extends StatefulWidget {
|
||||
final DateTime date;
|
||||
const DeliveryBarDetail(this.date);
|
||||
@override
|
||||
_DeliveryBarDetailState createState() => _DeliveryBarDetailState();
|
||||
}
|
||||
|
||||
class _DeliveryBarDetailState extends State<DeliveryBarDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd-MMM-yyyy');
|
||||
bool _isLoading = false;
|
||||
List<DOSubmission> dos = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
DOModel dOModel = Provider.of<DOModel>(context, listen: false);
|
||||
dOModel.getDOForDelivery(widget.date).then((dos) {
|
||||
setState(() {
|
||||
this.dos = dos;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'delivery.detail.title',
|
||||
translationVariables: [dateFormatter.format(widget.date)],
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 3),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "do.name")),
|
||||
MyDataColumn(label: LocalText(context, "do.do_num")),
|
||||
MyDataColumn(label: LocalText(context, "do.quantity"),numeric: true),
|
||||
],
|
||||
rows: getProductRow(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow() {
|
||||
return dos.map((d) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(d.userName, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(d.doNumber, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.totalQty)
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
78
lib/charts/delivery_summary.dart
Normal file
78
lib/charts/delivery_summary.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'delivery_summary_detail.dart';
|
||||
|
||||
class DeliverySummary extends StatefulWidget {
|
||||
@override
|
||||
_DeliverySummaryState createState() => _DeliverySummaryState();
|
||||
}
|
||||
|
||||
class _DeliverySummaryState extends State<DeliverySummary> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
List<charts.Series<Data, String>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: chartModel.revenue.getDeliverySummary(),
|
||||
domainFn: (Data series, _) => "${series.totalDay}days",
|
||||
measureFn: (Data series, _) => series.totalAmount,
|
||||
labelAccessorFn: (Data series, _) =>
|
||||
'${numberFormatter.format(series.totalAmount)}'),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, "delivery.summary",
|
||||
color: primaryColor, fontSize: 16),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"delivery.detail",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (_) => DeliverySummaryDetail()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.BarChart(
|
||||
series,
|
||||
animate: true,
|
||||
vertical: false,
|
||||
defaultRenderer: new charts.BarRendererConfig(
|
||||
barRendererDecorator: new charts.BarLabelDecorator<String>(
|
||||
labelPosition: charts.BarLabelPosition.auto,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
85
lib/charts/delivery_summary_detail.dart
Normal file
85
lib/charts/delivery_summary_detail.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DeliverySummaryDetail extends StatefulWidget {
|
||||
const DeliverySummaryDetail();
|
||||
@override
|
||||
_DeliverySummaryDetailState createState() => _DeliverySummaryDetailState();
|
||||
}
|
||||
|
||||
class _DeliverySummaryDetailState extends State<DeliverySummaryDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'delivery.sum.amounts',
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "delivery.days")),
|
||||
MyDataColumn(label: LocalText(context, "delivery.amount")),
|
||||
],
|
||||
rows: getProductRow(chartModel.revenue.getDeliverySummary()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<Data> doList) {
|
||||
return doList.map((d) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(d.totalDay.toString(), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
numberFormatter.format(d.totalAmount),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
156
lib/charts/do_line.dart
Normal file
156
lib/charts/do_line.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po_do_count.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
import 'do_line_detail.dart';
|
||||
|
||||
class DOLineChart extends StatefulWidget {
|
||||
@override
|
||||
_DOLineChartState createState() => _DOLineChartState();
|
||||
}
|
||||
|
||||
class _DOLineChartState extends State<DOLineChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
List<charts.Series<CountData, DateTime>> series = [
|
||||
charts.Series(
|
||||
id: "pending",
|
||||
data: chartModel.podoCount.getDODataCounts("pending"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 3, 2, 3],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "approved",
|
||||
data: chartModel.podoCount.getDODataCounts("approved"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "canceled",
|
||||
data: chartModel.podoCount.getDODataCounts("canceled"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.gray.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 3, 2, 3],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "rejected",
|
||||
data: chartModel.podoCount.getDODataCounts("rejected"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 3, 2, 3],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "expired",
|
||||
data: chartModel.podoCount.getDODataCounts("expired"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.purple.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 5, 2, 5],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "closed",
|
||||
data: chartModel.podoCount.getDODataCounts("closed"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.indigo.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 5, 2, 5],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do", color: primaryColor, fontSize: 16),
|
||||
LocalText(context, 'chart.30_days',
|
||||
color: primaryColor, fontSize: 14)
|
||||
],
|
||||
),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"do.details",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => DOLineDetail()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.TimeSeriesChart(
|
||||
series,
|
||||
animate: true,
|
||||
defaultRenderer: new charts.LineRendererConfig(
|
||||
includePoints: true,
|
||||
),
|
||||
behaviors: [
|
||||
new charts.SeriesLegend(
|
||||
position: charts.BehaviorPosition.end,
|
||||
outsideJustification:
|
||||
charts.OutsideJustification.middleDrawArea,
|
||||
entryTextStyle: charts.TextStyleSpec(
|
||||
color: charts.Color(r: 127, g: 63, b: 191),
|
||||
fontFamily: 'Georgia',
|
||||
fontSize: 11),
|
||||
)
|
||||
],
|
||||
primaryMeasureAxis: new charts.NumericAxisSpec(
|
||||
tickProviderSpec: new charts.BasicNumericTickProviderSpec(
|
||||
zeroBound: false, desiredTickCount: 10),
|
||||
renderSpec: new charts.GridlineRendererSpec(
|
||||
lineStyle: charts.LineStyleSpec(
|
||||
dashPattern: [4, 4],
|
||||
))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
121
lib/charts/do_line_detail.dart
Normal file
121
lib/charts/do_line_detail.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po_do_count.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DOLineDetail extends StatefulWidget {
|
||||
const DOLineDetail();
|
||||
@override
|
||||
_DOLineDetailState createState() => _DOLineDetailState();
|
||||
}
|
||||
|
||||
class _DOLineDetailState extends State<DOLineDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'do.counts',
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, "chart.date"),
|
||||
LocalText(context, "do.total_count")
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: getRowTotalCountWidget(
|
||||
chartModel.podoCount.getDOTotalCounts()),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> getRowTotalCountWidget(List<TotalCountData> data) {
|
||||
return data.map((d) {
|
||||
return Container(
|
||||
child: ExpansionTile(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(dateFormatter.format(d.date), style: textStyle),
|
||||
Text(numberFormatter.format(d.totalCount), style: textStyle),
|
||||
],
|
||||
),
|
||||
children: <Widget>[
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "do.count.status")),
|
||||
MyDataColumn(label: LocalText(context, "do.count")),
|
||||
],
|
||||
rows: getStatusRow(d.detailCountsList),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<MyDataRow> getStatusRow(List<CountData> doList) {
|
||||
doList.sort((a, b) => a.status.compareTo(b.status));
|
||||
return doList.map((d) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(d.status, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
numberFormatter.format(d.count),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
107
lib/charts/lines.dart
Normal file
107
lib/charts/lines.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../model/product_model.dart';
|
||||
|
||||
typedef void ProductClick(DateTime date, Map<String, int> measures);
|
||||
|
||||
class ProductsChart extends StatelessWidget {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
final ChartData chartData;
|
||||
final ProductClick productClick;
|
||||
const ProductsChart(this.chartData, {Key key, this.productClick})
|
||||
: super(key: key);
|
||||
factory ProductsChart.fromModel(ProductModel productModel,
|
||||
{ProductClick productClick}) {
|
||||
return new ProductsChart(_createData(productModel),
|
||||
productClick: productClick);
|
||||
}
|
||||
|
||||
static ChartData _createData(ProductModel productModel) {
|
||||
List<charts.Series<TimeSeriesSales, DateTime>> list = [];
|
||||
var min = 9999, max = 0;
|
||||
productModel.products.forEach((p) {
|
||||
List<TimeSeriesSales> data = [];
|
||||
if (p.priceHistory != null) {
|
||||
var dateKeys = {};
|
||||
p.priceHistory.entries.forEach((e) {
|
||||
dateKeys[DateTime.parse(e.key)] = e.value;
|
||||
});
|
||||
|
||||
var sortedKeys = dateKeys.keys.toList()..sort((a, b) => b.compareTo(a));
|
||||
sortedKeys.forEach((k) {
|
||||
var v = dateKeys[k];
|
||||
data.add(new TimeSeriesSales(k, v));
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
});
|
||||
}
|
||||
|
||||
list.add(new charts.Series<TimeSeriesSales, DateTime>(
|
||||
id: p.name,
|
||||
colorFn: (_, __) => charts.ColorUtil.fromDartColor(Color(p.color)),
|
||||
domainFn: (TimeSeriesSales sales, _) => sales.time,
|
||||
measureFn: (TimeSeriesSales sales, _) => sales.sales,
|
||||
data: data,
|
||||
labelAccessorFn: (TimeSeriesSales series, _) =>
|
||||
'${numberFormatter.format(series.sales)}',
|
||||
measureFormatterFn: (TimeSeriesSales series, _) => (n) => "s",
|
||||
));
|
||||
});
|
||||
var chartData = ChartData(list, min, max);
|
||||
return chartData;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return charts.TimeSeriesChart(
|
||||
chartData.seriesList,
|
||||
animate: true,
|
||||
defaultRenderer: new charts.LineRendererConfig(
|
||||
includePoints: true,
|
||||
),
|
||||
primaryMeasureAxis: new charts.NumericAxisSpec(
|
||||
tickProviderSpec: new charts.BasicNumericTickProviderSpec(
|
||||
zeroBound: false, desiredTickCount: 10),
|
||||
renderSpec: new charts.GridlineRendererSpec(
|
||||
lineStyle: charts.LineStyleSpec(
|
||||
dashPattern: [4, 4],
|
||||
))),
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
updatedListener: _onSelectionChanged,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_onSelectionChanged(charts.SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
if (selectedDatum.isNotEmpty) {
|
||||
var _time = selectedDatum.first.datum.time;
|
||||
Map<String, int> _measures = <String, int>{};
|
||||
|
||||
selectedDatum.forEach((charts.SeriesDatum datumPair) {
|
||||
_measures[datumPair.series.displayName] = datumPair.datum.sales;
|
||||
});
|
||||
if (productClick != null) {
|
||||
productClick(_time, _measures);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TimeSeriesSales {
|
||||
final DateTime time;
|
||||
final int sales;
|
||||
|
||||
TimeSeriesSales(this.time, this.sales);
|
||||
}
|
||||
|
||||
class ChartData {
|
||||
final List<charts.Series> seriesList;
|
||||
final num min, max;
|
||||
ChartData(this.seriesList, this.min, this.max);
|
||||
}
|
||||
134
lib/charts/po_balance_chart.dart
Normal file
134
lib/charts/po_balance_chart.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:charts_flutter/flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/charts/po_balance_table.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
class POBalanceChart extends StatefulWidget {
|
||||
@override
|
||||
_POBalanceChartState createState() => _POBalanceChartState();
|
||||
}
|
||||
|
||||
class _POBalanceChartState extends State<POBalanceChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
List<POChartData> chartSummary = new List();
|
||||
List<charts.Series<POChartData, String>> series;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var chartModel = Provider.of<ChartModel>(context, listen: false);
|
||||
if (mounted) {
|
||||
load(chartModel);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> load(ChartModel chartModel) async {
|
||||
var _u = await chartModel.loadPOBalancesForBuyer();
|
||||
if (_u == null) return;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
this.chartSummary = _u;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var productModel = Provider.of<ProductModel>(context);
|
||||
if (this.chartSummary.isNotEmpty) {
|
||||
this.chartSummary.forEach((s) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == s.productID) {
|
||||
s.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this
|
||||
.chartSummary
|
||||
.sort((s1, s2) => s1.displayOrder.compareTo(s2.displayOrder));
|
||||
}
|
||||
List<charts.Series<POChartData, String>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: this.chartSummary,
|
||||
domainFn: (POChartData series, _) => series.productName,
|
||||
measureFn: (POChartData series, _) => series.balanceQty,
|
||||
colorFn: (POChartData series, _) =>
|
||||
charts.ColorUtil.fromDartColor(series.getColor),
|
||||
labelAccessorFn: (POChartData series, _) =>
|
||||
'${numberFormatter.format(series.balanceQty)}'),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, 'po.balances',
|
||||
color: primaryColor, fontSize: 16),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.refresh,
|
||||
color: primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
_load();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.BarChart(
|
||||
series,
|
||||
animate: true,
|
||||
vertical: false,
|
||||
defaultRenderer: new charts.BarRendererConfig(
|
||||
barRendererDecorator: new charts.BarLabelDecorator<String>(
|
||||
labelPosition: charts.BarLabelPosition.auto,
|
||||
),
|
||||
),
|
||||
selectionModels: [
|
||||
SelectionModelConfig(changedListener: (SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
if (selectedDatum.isNotEmpty) {
|
||||
selectedDatum.forEach((charts.SeriesDatum datumPair) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => POBalanceTable(
|
||||
poChartData: datumPair.datum,
|
||||
)),
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
var _s = await chartModel.loadPOBalancesForBuyer();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
this.chartSummary = _s ?? [];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
106
lib/charts/po_balance_table.dart
Normal file
106
lib/charts/po_balance_table.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class POBalanceTable extends StatefulWidget {
|
||||
final POChartData poChartData;
|
||||
const POBalanceTable({Key key, this.poChartData}) : super(key: key);
|
||||
@override
|
||||
_POBalanceTableState createState() => _POBalanceTableState();
|
||||
}
|
||||
|
||||
class _POBalanceTableState extends State<POBalanceTable> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
List<POChartData> chartUser = new List();
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var chartModel = Provider.of<ChartModel>(context, listen: false);
|
||||
if (mounted) {
|
||||
load(chartModel);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> load(ChartModel chartModel) async {
|
||||
var _u = await chartModel.loadPOBalProductsForBuyer();
|
||||
setState(() {
|
||||
this.chartUser = _u;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<POChartData> data = this
|
||||
.chartUser
|
||||
.where((u) => u.productName == widget.poChartData.productName)
|
||||
.toList();
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(
|
||||
AppTranslations.of(context).text("product.qtys"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode'),
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 40,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "buyer.name")),
|
||||
MyDataColumn(label: LocalText(context, "buyer.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "buyer.balQty"), numeric: true),
|
||||
],
|
||||
rows: getProductRow(data),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<POChartData> poLines) {
|
||||
return poLines.map((p) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(p.userName, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
p.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(p.balanceQty),
|
||||
//new Text(numberFormatter.format(p.balanceQty), style: textStyle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
98
lib/charts/po_balanceby_buyer.dart
Normal file
98
lib/charts/po_balanceby_buyer.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
class POBalanceChart_ extends StatefulWidget {
|
||||
@override
|
||||
_POBalanceChartState createState() => _POBalanceChartState();
|
||||
|
||||
|
||||
}
|
||||
|
||||
class _POBalanceChartState extends State<POBalanceChart_> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
List<POBuyerData> chartSummary = new List();
|
||||
List<charts.Series<POBuyerData, String>> series;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var chartModel = Provider.of<ChartModel>(context, listen: false);
|
||||
if (mounted) {
|
||||
load(chartModel);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> load(ChartModel chartModel) async {
|
||||
var _u = await chartModel.loadPOBalancesForBuyer_();
|
||||
if (_u == null) return;
|
||||
setState(() {
|
||||
this.chartSummary = _u;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<charts.Series<POBuyerData, String>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: this.chartSummary,
|
||||
domainFn: (POBuyerData series, _) => series.status,
|
||||
measureFn: (POBuyerData series, _) => series.amount,
|
||||
colorFn: (POBuyerData series, _) =>
|
||||
charts.ColorUtil.fromDartColor(series.getColor),
|
||||
labelAccessorFn: (POBuyerData series, _) =>
|
||||
'${numberFormatter.format(series.amount)}'),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, 'po.balances',
|
||||
color: primaryColor, fontSize: 16),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.refresh,
|
||||
color: primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
_load();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.BarChart(
|
||||
series,
|
||||
animate: true,
|
||||
vertical: false,
|
||||
defaultRenderer: new charts.BarRendererConfig(
|
||||
barRendererDecorator: new charts.BarLabelDecorator<String>(
|
||||
labelPosition: charts.BarLabelPosition.auto,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
var _s = await chartModel.loadPOBalancesForBuyer_();
|
||||
if (_s == null) return;
|
||||
setState(() {
|
||||
this.chartSummary = _s;
|
||||
});
|
||||
}
|
||||
}
|
||||
155
lib/charts/po_line.dart
Normal file
155
lib/charts/po_line.dart
Normal file
@@ -0,0 +1,155 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/charts/po_line_detail.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po_do_count.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
class POLineChart extends StatefulWidget {
|
||||
@override
|
||||
_POLineChartState createState() => _POLineChartState();
|
||||
}
|
||||
|
||||
class _POLineChartState extends State<POLineChart> {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
List<charts.Series<CountData, DateTime>> series = [
|
||||
charts.Series(
|
||||
id: "pending",
|
||||
data: chartModel.podoCount.getPODataCounts('pending'),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 3, 2, 3],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "approved",
|
||||
data: chartModel.podoCount.getPODataCounts("approved"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "canceled",
|
||||
data: chartModel.podoCount.getPODataCounts("canceled"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.gray.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 3, 2, 3],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "rejected",
|
||||
data: chartModel.podoCount.getPODataCounts("rejected"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 3, 2, 3],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "expired",
|
||||
data: chartModel.podoCount.getPODataCounts("expired"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.purple.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 5, 2, 5],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
charts.Series(
|
||||
id: "closed",
|
||||
data: chartModel.podoCount.getPODataCounts("closed"),
|
||||
domainFn: (CountData series, _) => series.date,
|
||||
measureFn: (CountData series, _) =>
|
||||
series.count == null ? 0 : series.count,
|
||||
colorFn: (_, __) => charts.MaterialPalette.indigo.shadeDefault,
|
||||
dashPatternFn: (_, __) => [8, 5, 2, 5],
|
||||
labelAccessorFn: (CountData series, _) =>
|
||||
'${numberFormatter.format(series.count)}',
|
||||
),
|
||||
];
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "po", color: primaryColor, fontSize: 16),
|
||||
LocalText(context, 'chart.30_days',
|
||||
color: primaryColor, fontSize: 14)
|
||||
],
|
||||
),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"po.details",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => POLineDetail()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.TimeSeriesChart(
|
||||
series,
|
||||
animate: true,
|
||||
defaultRenderer: new charts.LineRendererConfig(
|
||||
includePoints: true,
|
||||
),
|
||||
behaviors: [
|
||||
new charts.SeriesLegend(
|
||||
position: charts.BehaviorPosition.end,
|
||||
outsideJustification:
|
||||
charts.OutsideJustification.middleDrawArea,
|
||||
entryTextStyle: charts.TextStyleSpec(
|
||||
color: charts.Color(r: 127, g: 63, b: 191),
|
||||
fontFamily: 'Georgia',
|
||||
fontSize: 11),
|
||||
)
|
||||
],
|
||||
primaryMeasureAxis: new charts.NumericAxisSpec(
|
||||
tickProviderSpec: new charts.BasicNumericTickProviderSpec(
|
||||
zeroBound: false, desiredTickCount: 10),
|
||||
renderSpec: new charts.GridlineRendererSpec(
|
||||
lineStyle: charts.LineStyleSpec(
|
||||
dashPattern: [4, 4],
|
||||
))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
121
lib/charts/po_line_detail.dart
Normal file
121
lib/charts/po_line_detail.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po_do_count.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class POLineDetail extends StatefulWidget {
|
||||
const POLineDetail();
|
||||
@override
|
||||
_POLineDetailState createState() => _POLineDetailState();
|
||||
}
|
||||
|
||||
class _POLineDetailState extends State<POLineDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'po.counts',
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
LocalText(context, "chart.date"),
|
||||
LocalText(context, "po.total_count")
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: getRowTotalCountWidget(
|
||||
chartModel.podoCount.getPOTotalCounts()),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> getRowTotalCountWidget(List<TotalCountData> data) {
|
||||
return data.map((d) {
|
||||
return Container(
|
||||
child: ExpansionTile(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(dateFormatter.format(d.date), style: textStyle),
|
||||
Text(numberFormatter.format(d.totalCount), style: textStyle),
|
||||
],
|
||||
),
|
||||
children: <Widget>[
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "po.count.status")),
|
||||
MyDataColumn(label: LocalText(context, "po.count")),
|
||||
],
|
||||
rows: getStatusRow(d.detailCountsList),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<MyDataRow> getStatusRow(List<CountData> po) {
|
||||
po.sort((a, b) => a.status.compareTo(b.status));
|
||||
return po.map((p) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(p.status, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
p.count != null ? numberFormatter.format(p.count) : '0',
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
103
lib/charts/qtyby_customer_table.dart
Normal file
103
lib/charts/qtyby_customer_table.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class QtyByCustomerTable extends StatefulWidget {
|
||||
final POChartData poChartData;
|
||||
const QtyByCustomerTable({Key key, this.poChartData}) : super(key: key);
|
||||
@override
|
||||
_QtyByCustomerTableState createState() => _QtyByCustomerTableState();
|
||||
}
|
||||
|
||||
class _QtyByCustomerTableState extends State<QtyByCustomerTable> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
List<POChartData> chartUser = new List();
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var chartModel = Provider.of<ChartModel>(context, listen: false);
|
||||
if (mounted) {
|
||||
load(chartModel);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> load(ChartModel chartModel) async {
|
||||
var _u = await chartModel.loadUsers();
|
||||
setState(() {
|
||||
this.chartUser = _u;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<POChartData> data = this
|
||||
.chartUser
|
||||
.where((u) => u.productName == widget.poChartData.productName)
|
||||
.toList();
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(
|
||||
AppTranslations.of(context).text("product.qtys"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode'),
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 40,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "buyer.name")),
|
||||
MyDataColumn(label: LocalText(context, "buyer.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "buyer.balQty"), numeric: true),
|
||||
],
|
||||
rows: getProductRow(data),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<POChartData> poLines) {
|
||||
return poLines.map((p) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(p.userName, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
p.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(NumberCell(p.balanceQty)),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
118
lib/charts/quota.dart
Normal file
118
lib/charts/quota.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
/// Simple pie chart example.
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:fcs/vo/buyer.dart';
|
||||
import 'package:fcs/vo/product.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
|
||||
class QuotaChart extends StatelessWidget {
|
||||
static final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
final List<charts.Series> quotaSeries;
|
||||
final String title;
|
||||
|
||||
QuotaChart(this.quotaSeries, this.title);
|
||||
|
||||
factory QuotaChart.dailyQuota(
|
||||
BuildContext context, Buyer buyer, List<Product> products) {
|
||||
List<Quota> data = [];
|
||||
products.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
products.forEach((p) {
|
||||
if (buyer.dailyQuotaUsedProducts.containsKey(p.id)) {
|
||||
int value = buyer.dailyQuotaUsedProducts[p.id];
|
||||
data.add(Quota(p.name, value, p.color));
|
||||
}
|
||||
});
|
||||
data.add(
|
||||
new Quota(AppTranslations.of(context).text("chart.remaining"),
|
||||
buyer.dailyQuota - buyer.dailyQuotaUsed, Colors.purple.value),
|
||||
);
|
||||
|
||||
return new QuotaChart(
|
||||
_createData(data),
|
||||
AppTranslations.of(context).text("chart.daily.title",
|
||||
translationVariables: [numberFormatter.format(buyer.dailyQuota)]));
|
||||
}
|
||||
|
||||
factory QuotaChart.maxQuota(
|
||||
BuildContext context, Buyer buyer, List<Product> products) {
|
||||
List<Quota> data = [];
|
||||
products.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
products.forEach((p) {
|
||||
if (buyer.maxQuotaUsedProducts.containsKey(p.id)) {
|
||||
int value = buyer.maxQuotaUsedProducts[p.id];
|
||||
data.add(Quota(p.name, value, p.color));
|
||||
}
|
||||
});
|
||||
data.add(
|
||||
new Quota(AppTranslations.of(context).text("chart.remaining"),
|
||||
buyer.maxQuota - buyer.maxQuotaUsed, Colors.purple.value),
|
||||
);
|
||||
return new QuotaChart(
|
||||
_createData(data),
|
||||
AppTranslations.of(context).text("chart.max.title",
|
||||
translationVariables: [numberFormatter.format(buyer.maxQuota)]));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Text(title),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
height: 200,
|
||||
child: charts.PieChart(
|
||||
quotaSeries,
|
||||
animate: false,
|
||||
behaviors: [
|
||||
new charts.DatumLegend(
|
||||
position: charts.BehaviorPosition.end,
|
||||
horizontalFirst: false,
|
||||
cellPadding: new EdgeInsets.only(right: 4.0, bottom: 4.0),
|
||||
entryTextStyle: charts.TextStyleSpec(
|
||||
color: charts.MaterialPalette.purple.shadeDefault,
|
||||
fontFamily: 'Georgia',
|
||||
fontSize: 11),
|
||||
)
|
||||
],
|
||||
defaultRenderer: new charts.ArcRendererConfig(
|
||||
arcWidth: 60,
|
||||
arcRendererDecorators: [
|
||||
new charts.ArcLabelDecorator(
|
||||
labelPosition: charts.ArcLabelPosition.auto,
|
||||
labelPadding: 0)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static List<charts.Series<Quota, String>> _createData(List<Quota> data) {
|
||||
return [
|
||||
new charts.Series<Quota, String>(
|
||||
id: 'Daily Quota',
|
||||
domainFn: (Quota quota, _) => "${quota.label}\n",
|
||||
measureFn: (Quota quota, _) => quota.quota,
|
||||
data: data,
|
||||
colorFn: (Quota quota, i) =>
|
||||
charts.ColorUtil.fromDartColor(Color(quota.color)),
|
||||
labelAccessorFn: (Quota row, _) =>
|
||||
'${numberFormatter.format(row.quota)}',
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Quota {
|
||||
final String label;
|
||||
final int quota;
|
||||
final int color;
|
||||
|
||||
Quota(this.label, this.quota, this.color);
|
||||
}
|
||||
118
lib/charts/revenue_line.dart
Normal file
118
lib/charts/revenue_line.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
import 'revenue_line_data.dart';
|
||||
|
||||
class RevenueLineChart extends StatefulWidget {
|
||||
@override
|
||||
_RevenueLineChartState createState() => _RevenueLineChartState();
|
||||
}
|
||||
|
||||
class _RevenueLineChartState extends State<RevenueLineChart> {
|
||||
static final numberFormatter = NumberFormat.compact();
|
||||
int actualChart = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
|
||||
List<charts.Series<Data, DateTime>> series = [
|
||||
charts.Series(
|
||||
id: "Subscribers",
|
||||
data: chartModel.revenue.getData(),
|
||||
domainFn: (Data series, _) => series.date,
|
||||
measureFn: (Data series, _) => series.amount,
|
||||
colorFn: (_, __) => charts.ColorUtil.fromDartColor(primaryColor),
|
||||
labelAccessorFn: (Data series, _) =>
|
||||
'${numberFormatter.format(series.amount)}',
|
||||
),
|
||||
];
|
||||
|
||||
final moneyFormatter =
|
||||
new charts.BasicNumericTickFormatterSpec.fromNumberFormat(
|
||||
new NumberFormat.compact());
|
||||
|
||||
return Container(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
mainModel.user.isOwnerAndAbove()
|
||||
? Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, 'chart.revenue',
|
||||
fontSize: 16, color: primaryColor),
|
||||
LocalText(context, 'chart.30_days',
|
||||
color: primaryColor, fontSize: 14),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, 'chart.spending',
|
||||
fontSize: 16, color: primaryColor),
|
||||
LocalText(context, 'chart.30_days',
|
||||
color: primaryColor, fontSize: 14)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
"${chartModel.revenue.mapData == null ? "" : numberFormatter.format(chartModel.revenue.getTotal(actualChart))}",
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 17.0))
|
||||
],
|
||||
),
|
||||
InkWell(
|
||||
child: LocalText(
|
||||
context,
|
||||
"revenue.detail",
|
||||
color: secondaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => RevenueLineData()));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: charts.TimeSeriesChart(
|
||||
series,
|
||||
animate: true,
|
||||
defaultRenderer: new charts.LineRendererConfig(
|
||||
includePoints: true,
|
||||
),
|
||||
primaryMeasureAxis: new charts.NumericAxisSpec(
|
||||
tickFormatterSpec: moneyFormatter,
|
||||
tickProviderSpec: new charts.BasicNumericTickProviderSpec(
|
||||
zeroBound: false, desiredTickCount: 10),
|
||||
renderSpec: new charts.GridlineRendererSpec(
|
||||
lineStyle: charts.LineStyleSpec(
|
||||
dashPattern: [4, 4],
|
||||
))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
92
lib/charts/revenue_line_data.dart
Normal file
92
lib/charts/revenue_line_data.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/chart_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'revenue_line_detail.dart';
|
||||
|
||||
class RevenueLineData extends StatefulWidget {
|
||||
const RevenueLineData();
|
||||
@override
|
||||
_RevenueLineDataState createState() => _RevenueLineDataState();
|
||||
}
|
||||
|
||||
class _RevenueLineDataState extends State<RevenueLineData> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chartModel = Provider.of<ChartModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: mainModel.user.isOwnerAndAbove()
|
||||
? LocalText(context, 'revenue.amounts',
|
||||
color: Colors.white, fontSize: 18)
|
||||
: LocalText(context, 'spending.amounts',
|
||||
color: Colors.white, fontSize: 18),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "revenue.date")),
|
||||
MyDataColumn(label: LocalText(context, "revenue.amount"),numeric: true),
|
||||
],
|
||||
rows: getProductRow(chartModel.revenue.getData()),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<Data> revs) {
|
||||
return revs.map((p) {
|
||||
var r = MyDataRow(
|
||||
onSelectChanged: (bool selected) async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => RevenueLineDetail(p.date)),
|
||||
);
|
||||
},
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(dateFormatter.format(p.date), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(p.amount)
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
106
lib/charts/revenue_line_detail.dart
Normal file
106
lib/charts/revenue_line_detail.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/po_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class RevenueLineDetail extends StatefulWidget {
|
||||
final DateTime date;
|
||||
const RevenueLineDetail(this.date);
|
||||
@override
|
||||
_RevenueLineDetailState createState() => _RevenueLineDetailState();
|
||||
}
|
||||
|
||||
class _RevenueLineDetailState extends State<RevenueLineDetail> {
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var dateFormatter = new DateFormat('dd-MMM-yyyy');
|
||||
bool _isLoading = false;
|
||||
List<POSubmission> pos = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
POSubmissionModel pOModel =
|
||||
Provider.of<POSubmissionModel>(context, listen: false);
|
||||
pOModel.getPOForRevenue(widget.date).then((pos) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
this.pos = pos;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: mainModel.user.isOwnerAndAbove()
|
||||
? LocalText(
|
||||
context,
|
||||
'revenue.detail.title',
|
||||
translationVariables: [dateFormatter.format(widget.date)],
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
)
|
||||
: LocalText(
|
||||
context,
|
||||
'spending.detail.title',
|
||||
translationVariables: [dateFormatter.format(widget.date)],
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(left: 3),
|
||||
child: MyDataTable(
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "po.name")),
|
||||
MyDataColumn(label: LocalText(context, "po.po_num")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.amount"), numeric: true),
|
||||
],
|
||||
rows: getProductRow(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow() {
|
||||
return pos.map((p) {
|
||||
var r = MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(p.userName, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(p.poNumber, style: textStyle),
|
||||
),
|
||||
MyDataCell(NumberCell(p.amount)),
|
||||
],
|
||||
);
|
||||
|
||||
return r;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
60
lib/charts/time.dart
Normal file
60
lib/charts/time.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
/// Timeseries chart example
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SimpleTimeSeriesChart extends StatelessWidget {
|
||||
final List<charts.Series> seriesList;
|
||||
final bool animate;
|
||||
|
||||
SimpleTimeSeriesChart(this.seriesList, {this.animate});
|
||||
|
||||
/// Creates a [TimeSeriesChart] with sample data and no transition.
|
||||
factory SimpleTimeSeriesChart.withSampleData() {
|
||||
return new SimpleTimeSeriesChart(
|
||||
_createSampleData(),
|
||||
// Disable animations for image tests.
|
||||
animate: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new charts.TimeSeriesChart(
|
||||
seriesList,
|
||||
animate: animate,
|
||||
// Optionally pass in a [DateTimeFactory] used by the chart. The factory
|
||||
// should create the same type of [DateTime] as the data provided. If none
|
||||
// specified, the default creates local date time.
|
||||
dateTimeFactory: const charts.LocalDateTimeFactory(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create one series with sample hard coded data.
|
||||
static List<charts.Series<TimeSeriesSales, DateTime>> _createSampleData() {
|
||||
final data = [
|
||||
new TimeSeriesSales(new DateTime(2017, 9, 19), 5),
|
||||
new TimeSeriesSales(new DateTime(2017, 9, 26), 25),
|
||||
new TimeSeriesSales(new DateTime(2017, 10, 3), 100),
|
||||
new TimeSeriesSales(new DateTime(2017, 10, 10), 75),
|
||||
];
|
||||
|
||||
return [
|
||||
new charts.Series<TimeSeriesSales, DateTime>(
|
||||
id: 'Sales',
|
||||
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
|
||||
domainFn: (TimeSeriesSales sales, _) => sales.time,
|
||||
measureFn: (TimeSeriesSales sales, _) => sales.sales,
|
||||
data: data,
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample time series data type.
|
||||
class TimeSeriesSales {
|
||||
final DateTime time;
|
||||
final int sales;
|
||||
|
||||
TimeSeriesSales(this.time, this.sales);
|
||||
}
|
||||
48
lib/config.dart
Normal file
48
lib/config.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
enum Flavor { DEV, STAGING, PRODUCTION, LOCAL }
|
||||
const FlavorNames = ["Development", "Staging", "Production", "Local"];
|
||||
|
||||
class Config {
|
||||
static Config _instance;
|
||||
|
||||
final Flavor flavor;
|
||||
final String name;
|
||||
final Color color;
|
||||
final String apiURL;
|
||||
final String reportURL;
|
||||
final Level level;
|
||||
final String reportProjectID;
|
||||
|
||||
factory Config(
|
||||
{@required Flavor flavor,
|
||||
@required String apiURL,
|
||||
@required String reportURL,
|
||||
@required String reportProjectID,
|
||||
Color color: Colors.blue,
|
||||
Level level: Level.SEVERE}) {
|
||||
_instance ??= Config._internal(flavor, FlavorNames[flavor.index], color,
|
||||
apiURL, reportURL, level, reportProjectID);
|
||||
|
||||
Logger.root.level = level;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
print(
|
||||
'${record.level.name}: ${record.time}: ${record.loggerName}: ${record.message}');
|
||||
});
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
Config._internal(this.flavor, this.name, this.color, this.apiURL,
|
||||
this.reportURL, this.level, this.reportProjectID);
|
||||
|
||||
static Config get instance {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static bool isProduction() => _instance.flavor == Flavor.PRODUCTION;
|
||||
static bool isDevelopment() => _instance.flavor == Flavor.DEV;
|
||||
static bool isStaging() => _instance.flavor == Flavor.STAGING;
|
||||
static bool isLocal() => _instance.flavor == Flavor.LOCAL;
|
||||
}
|
||||
177
lib/face/face_detection_camera.dart
Normal file
177
lib/face/face_detection_camera.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import 'smile_painter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:ui' as ui show Image;
|
||||
import 'utils.dart';
|
||||
|
||||
class FaceDetectionFromLiveCamera extends StatefulWidget {
|
||||
FaceDetectionFromLiveCamera({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FaceDetectionFromLiveCameraState createState() =>
|
||||
_FaceDetectionFromLiveCameraState();
|
||||
}
|
||||
|
||||
class _FaceDetectionFromLiveCameraState
|
||||
extends State<FaceDetectionFromLiveCamera> {
|
||||
final FaceDetector faceDetector = FirebaseVision.instance.faceDetector();
|
||||
List<Face> faces;
|
||||
CameraController _camera;
|
||||
|
||||
bool _isDetecting = false;
|
||||
CameraLensDirection _direction = CameraLensDirection.back;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeCamera();
|
||||
}
|
||||
|
||||
void _initializeCamera() async {
|
||||
CameraDescription description = await getCamera(_direction);
|
||||
ImageRotation rotation = rotationIntToImageRotation(
|
||||
description.sensorOrientation,
|
||||
);
|
||||
|
||||
_camera = CameraController(
|
||||
description,
|
||||
defaultTargetPlatform == TargetPlatform.iOS
|
||||
? ResolutionPreset.low
|
||||
: ResolutionPreset.medium,
|
||||
);
|
||||
await _camera.initialize();
|
||||
|
||||
_camera.startImageStream((CameraImage image) {
|
||||
if (_isDetecting) return;
|
||||
|
||||
_isDetecting = true;
|
||||
|
||||
detect(
|
||||
image,
|
||||
FirebaseVision.instance
|
||||
.faceDetector(FaceDetectorOptions(
|
||||
mode: FaceDetectorMode.accurate,
|
||||
enableClassification: true))
|
||||
.processImage,
|
||||
rotation)
|
||||
.then(
|
||||
(dynamic result) {
|
||||
setState(() {
|
||||
faces = result;
|
||||
});
|
||||
|
||||
_isDetecting = false;
|
||||
},
|
||||
).catchError(
|
||||
(_) {
|
||||
_isDetecting = false;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildResults() {
|
||||
const Text noResultsText = const Text('No results!');
|
||||
const Text multipleFaceText = const Text('Multiple faces!');
|
||||
const Text pleaseSmileText = const Text('Please smile!');
|
||||
|
||||
if (faces == null || _camera == null || !_camera.value.isInitialized) {
|
||||
return noResultsText;
|
||||
}
|
||||
|
||||
CustomPainter painter;
|
||||
|
||||
final Size imageSize = Size(
|
||||
_camera.value.previewSize.height,
|
||||
_camera.value.previewSize.width,
|
||||
);
|
||||
|
||||
if (faces is! List<Face> ||
|
||||
faces.isEmpty ||
|
||||
faces == null ||
|
||||
faces.length == 0) return noResultsText;
|
||||
if (faces.length > 1) return multipleFaceText;
|
||||
var face = faces[0];
|
||||
if (face.smilingProbability == null || face.smilingProbability < 0.8) {
|
||||
return pleaseSmileText;
|
||||
}
|
||||
|
||||
painter = SmilePainterLiveCamera(imageSize, faces);
|
||||
return CustomPaint(
|
||||
painter: painter,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImage() {
|
||||
return Container(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: _camera == null
|
||||
? const Center(
|
||||
child: Text(
|
||||
'Initializing Camera...',
|
||||
style: TextStyle(
|
||||
color: Colors.green,
|
||||
fontSize: 30.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
CameraPreview(_camera),
|
||||
_buildResults(),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
height: 50.0,
|
||||
child: ListView(
|
||||
children: faces
|
||||
.map((face) => Text(
|
||||
"${face.boundingBox.center.toString()}, Smile:${face.smilingProbability}"))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleCameraDirection() async {
|
||||
if (_direction == CameraLensDirection.back) {
|
||||
_direction = CameraLensDirection.front;
|
||||
} else {
|
||||
_direction = CameraLensDirection.back;
|
||||
}
|
||||
|
||||
await _camera.stopImageStream();
|
||||
await _camera.dispose();
|
||||
|
||||
setState(() {
|
||||
_camera = null;
|
||||
});
|
||||
|
||||
_initializeCamera();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Face Detection with Smile"),
|
||||
),
|
||||
body: _buildImage(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _toggleCameraDirection,
|
||||
child: _direction == CameraLensDirection.back
|
||||
? const Icon(Icons.camera_front)
|
||||
: const Icon(Icons.camera_rear),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
59
lib/face/face_detection_image.dart
Normal file
59
lib/face/face_detection_image.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
import 'smile_painter.dart';
|
||||
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
|
||||
import 'dart:ui' as ui show Image;
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class FaceDetectionFromImage extends StatefulWidget {
|
||||
@override
|
||||
_FaceDetectionFromImageState createState() => _FaceDetectionFromImageState();
|
||||
}
|
||||
|
||||
class _FaceDetectionFromImageState extends State<FaceDetectionFromImage> {
|
||||
bool loading = true;
|
||||
ui.Image image;
|
||||
List<Face> faces;
|
||||
final FaceDetector faceDetector = FirebaseVision.instance.faceDetector();
|
||||
|
||||
Future<ui.Image> _loadImage(File file) async {
|
||||
final data = await file.readAsBytes();
|
||||
return await decodeImageFromList(data);
|
||||
}
|
||||
|
||||
void pickAndProcessImage() async {
|
||||
final File file = await ImagePicker.pickImage(source: ImageSource.gallery);
|
||||
final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(file);
|
||||
faces = await faceDetector.processImage(visionImage);
|
||||
image = await _loadImage(file);
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Face detection with Smile'),
|
||||
),
|
||||
body: Center(
|
||||
child: loading
|
||||
? Text('Press The floating Action Button for load image!')
|
||||
: FittedBox(
|
||||
child: SizedBox(
|
||||
width: image.width.toDouble(),
|
||||
height: image.height.toDouble(),
|
||||
child: FacePaint(
|
||||
painter: SmilePainter(image, faces),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: pickAndProcessImage,
|
||||
child: Icon(Icons.image),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
40
lib/face/home.dart
Normal file
40
lib/face/home.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'face_detection_camera.dart';
|
||||
import 'face_detection_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Smile To Face App'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
child: Text('Add Smile to Face from Image'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FaceDetectionFromImage(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
RaisedButton(
|
||||
child: Text('Add Smile to Face from Live Camera'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FaceDetectionFromLiveCamera(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
17
lib/face/main.dart
Normal file
17
lib/face/main.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'home.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: HomeScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
138
lib/face/smile_painter.dart
Normal file
138
lib/face/smile_painter.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
import 'dart:ui' as ui show Image;
|
||||
import 'dart:math' as Math;
|
||||
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FacePaint extends CustomPaint {
|
||||
final CustomPainter painter;
|
||||
|
||||
FacePaint({this.painter}) : super(painter: painter);
|
||||
}
|
||||
|
||||
class SmilePainter extends CustomPainter {
|
||||
final ui.Image image;
|
||||
final List<Face> faces;
|
||||
|
||||
SmilePainter(this.image, this.faces);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (image != null) {
|
||||
canvas.drawImage(image, Offset.zero, Paint());
|
||||
}
|
||||
|
||||
final paintRectStyle = Paint()
|
||||
..color = Colors.red
|
||||
..strokeWidth = 30.0
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
//Draw Body
|
||||
final paint = Paint()..color = Colors.yellow;
|
||||
|
||||
for (var i = 0; i < faces.length; i++) {
|
||||
final radius =
|
||||
Math.min(faces[i].boundingBox.width, faces[i].boundingBox.height) / 2;
|
||||
final center = faces[i].boundingBox.center;
|
||||
final smilePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = radius / 8;
|
||||
canvas.drawRect(faces[i].boundingBox, paintRectStyle);
|
||||
canvas.drawCircle(center, radius, paint);
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(
|
||||
center: center.translate(0, radius / 8), radius: radius / 2),
|
||||
0,
|
||||
Math.pi,
|
||||
false,
|
||||
smilePaint);
|
||||
//Draw the eyes
|
||||
canvas.drawCircle(Offset(center.dx - radius / 2, center.dy - radius / 2),
|
||||
radius / 8, Paint());
|
||||
canvas.drawCircle(Offset(center.dx + radius / 2, center.dy - radius / 2),
|
||||
radius / 8, Paint());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(SmilePainter oldDelegate) {
|
||||
return image != oldDelegate.image || faces != oldDelegate.faces;
|
||||
}
|
||||
}
|
||||
|
||||
class SmilePainterLiveCamera extends CustomPainter {
|
||||
final Size imageSize;
|
||||
final List<Face> faces;
|
||||
|
||||
SmilePainterLiveCamera(this.imageSize, this.faces);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// final paintRectStyle = Paint()
|
||||
// ..color = Colors.red
|
||||
// ..strokeWidth = 10.0
|
||||
// ..style = PaintingStyle.stroke;
|
||||
|
||||
final paint = Paint()..color = Colors.yellow;
|
||||
|
||||
for (var i = 0; i < faces.length; i++) {
|
||||
//Scale rect to image size
|
||||
final rect = _scaleRect(
|
||||
rect: faces[i].boundingBox,
|
||||
imageSize: imageSize,
|
||||
widgetSize: size,
|
||||
);
|
||||
|
||||
//Radius for smile circle
|
||||
final radius = Math.min(rect.width, rect.height) / 2;
|
||||
|
||||
//Center of face rect
|
||||
final Offset center = rect.center;
|
||||
|
||||
final smilePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = radius / 8;
|
||||
|
||||
//Draw rect border
|
||||
//canvas.drawRect(rect, paintRectStyle);
|
||||
|
||||
//Draw body
|
||||
canvas.drawCircle(center, radius, paint);
|
||||
|
||||
//Draw mouth
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(
|
||||
center: center.translate(0, radius / 8), radius: radius / 2),
|
||||
0,
|
||||
Math.pi,
|
||||
false,
|
||||
smilePaint);
|
||||
|
||||
//Draw the eyes
|
||||
canvas.drawCircle(Offset(center.dx - radius / 2, center.dy - radius / 2),
|
||||
radius / 8, Paint());
|
||||
canvas.drawCircle(Offset(center.dx + radius / 2, center.dy - radius / 2),
|
||||
radius / 8, Paint());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(SmilePainterLiveCamera oldDelegate) {
|
||||
return imageSize != oldDelegate.imageSize || faces != oldDelegate.faces;
|
||||
}
|
||||
}
|
||||
|
||||
Rect _scaleRect({
|
||||
@required Rect rect,
|
||||
@required Size imageSize,
|
||||
@required Size widgetSize,
|
||||
}) {
|
||||
final double scaleX = widgetSize.width / imageSize.width;
|
||||
final double scaleY = widgetSize.height / imageSize.height;
|
||||
|
||||
return Rect.fromLTRB(
|
||||
rect.left.toDouble() * scaleX,
|
||||
rect.top.toDouble() * scaleY,
|
||||
rect.right.toDouble() * scaleX,
|
||||
rect.bottom.toDouble() * scaleY,
|
||||
);
|
||||
}
|
||||
69
lib/face/utils.dart
Normal file
69
lib/face/utils.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
typedef HandleDetection = Future<List<Face>> Function(FirebaseVisionImage image);
|
||||
|
||||
Future<CameraDescription> getCamera(CameraLensDirection dir) async {
|
||||
return await availableCameras().then(
|
||||
(List<CameraDescription> cameras) => cameras.firstWhere(
|
||||
(CameraDescription camera) => camera.lensDirection == dir,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Uint8List concatenatePlanes(List<Plane> planes) {
|
||||
final WriteBuffer allBytes = WriteBuffer();
|
||||
planes.forEach((Plane plane) => allBytes.putUint8List(plane.bytes));
|
||||
return allBytes.done().buffer.asUint8List();
|
||||
}
|
||||
|
||||
FirebaseVisionImageMetadata buildMetaData(
|
||||
CameraImage image,
|
||||
ImageRotation rotation,
|
||||
) {
|
||||
return FirebaseVisionImageMetadata(
|
||||
rawFormat: image.format.raw,
|
||||
size: Size(image.width.toDouble(), image.height.toDouble()),
|
||||
rotation: rotation,
|
||||
planeData: image.planes.map(
|
||||
(Plane plane) {
|
||||
return FirebaseVisionImagePlaneMetadata(
|
||||
bytesPerRow: plane.bytesPerRow,
|
||||
height: plane.height,
|
||||
width: plane.width,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Face>> detect(
|
||||
CameraImage image,
|
||||
HandleDetection handleDetection,
|
||||
ImageRotation rotation,
|
||||
) async {
|
||||
return handleDetection(
|
||||
FirebaseVisionImage.fromBytes(
|
||||
concatenatePlanes(image.planes),
|
||||
buildMetaData(image, rotation),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ImageRotation rotationIntToImageRotation(int rotation) {
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
return ImageRotation.rotation0;
|
||||
case 90:
|
||||
return ImageRotation.rotation90;
|
||||
case 180:
|
||||
return ImageRotation.rotation180;
|
||||
default:
|
||||
assert(rotation == 270);
|
||||
return ImageRotation.rotation270;
|
||||
}
|
||||
}
|
||||
17
lib/main-dev.dart
Normal file
17
lib/main-dev.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/config.dart';
|
||||
|
||||
import 'app.dart';
|
||||
|
||||
void main() {
|
||||
Config(
|
||||
flavor: Flavor.DEV,
|
||||
color: Colors.blue,
|
||||
apiURL:
|
||||
"https://asia-northeast1-mokkon-wholesale-dev.cloudfunctions.net/APIOK",
|
||||
reportURL: "http://petrok-dev.mokkon.com:8080",
|
||||
reportProjectID: "dev",
|
||||
level: Level.ALL);
|
||||
runApp(App());
|
||||
}
|
||||
117
lib/main.dart
117
lib/main.dart
@@ -1,117 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
// This makes the visual density adapt to the platform that you run
|
||||
// the app on. For desktop platforms, the controls will be smaller and
|
||||
// closer together (more dense) than on mobile platforms.
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
MyHomePage({Key key, this.title}) : super(key: key);
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
_MyHomePageState createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Invoke "debug painting" (press "p" in the console, choose the
|
||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||
// to see the wireframe for each widget.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
61
lib/model/announcement_model.dart
Normal file
61
lib/model/announcement_model.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fcs/vo/announcement.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class AnnouncementModel extends BaseModel {
|
||||
List<Announcement> announcements = [];
|
||||
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
_loadAnnouncements();
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
announcements = [];
|
||||
}
|
||||
|
||||
Future<void> _loadAnnouncements() async {
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(
|
||||
"/$biz_collection/${setting.okEnergyId}/$announcement_collection")
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
announcements = snaps.documents.map((documentSnapshot) {
|
||||
var data = Announcement.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
return data;
|
||||
}).toList();
|
||||
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Future<Announcement> getAnnouncement(String id) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$announcement_collection";
|
||||
var snap = await getDocSnap(path, id);
|
||||
return Announcement.fromMap(snap.data, snap.documentID);
|
||||
}
|
||||
|
||||
Future<void> createAnnouncement(Announcement announcement) async {
|
||||
await request("/announcement", "POST",
|
||||
payload: announcement.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateAnnouncement(Announcement announcement) async {
|
||||
await request("/announcement", "PUT",
|
||||
payload: announcement.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> deleteAnnouncement(Announcement announcement) async {
|
||||
await request("/announcement", "DELETE",
|
||||
payload: announcement.toMap(), token: await getToken());
|
||||
}
|
||||
}
|
||||
147
lib/model/api_helper.dart
Normal file
147
lib/model/api_helper.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/vo/status.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
final log = Logger('requestAPI');
|
||||
|
||||
// request makes http request
|
||||
// if token is null
|
||||
Future<dynamic> requestAPI(
|
||||
String path,
|
||||
method, {
|
||||
dynamic payload,
|
||||
String token,
|
||||
String url,
|
||||
}) async {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
String deviceName = "${androidInfo.model}(${androidInfo.id})";
|
||||
log.info("device:${androidInfo.androidId},deviceName:$deviceName");
|
||||
|
||||
Map<String, dynamic> headers = {};
|
||||
if (token != null) {
|
||||
headers["Token"] = token;
|
||||
}
|
||||
if (androidInfo.androidId != null) {
|
||||
headers["Device"] = androidInfo.androidId + ":" + deviceName;
|
||||
}
|
||||
headers["Project-ID"] = Config.instance.reportProjectID;
|
||||
|
||||
BaseOptions options = new BaseOptions(
|
||||
method: method,
|
||||
baseUrl: url == null ? Config.instance.apiURL : url,
|
||||
connectTimeout: 10000,
|
||||
receiveTimeout: 10000,
|
||||
headers: headers,
|
||||
);
|
||||
log.info("baseUrl:${options.baseUrl}, path:$path");
|
||||
try {
|
||||
Dio dio = new Dio(options);
|
||||
Response response = await dio.request(
|
||||
path,
|
||||
data: payload,
|
||||
);
|
||||
var data = Status.fromJson(response.data);
|
||||
if (data.status == 'Ok') {
|
||||
return response.data["data"];
|
||||
} else {
|
||||
throw Exception(data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning("path:$path, api:$e");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// request makes http request
|
||||
// if token is null
|
||||
Future<dynamic> requestDownloadAPI(String path, method,
|
||||
{dynamic payload, String token, String url, String filePath}) async {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
String deviceName = "${androidInfo.model}(${androidInfo.id})";
|
||||
log.info("device:${androidInfo.androidId},deviceName:$deviceName");
|
||||
|
||||
var bytes = utf8.encode(payload);
|
||||
var base64Str = base64.encode(bytes);
|
||||
String escapePayload = HtmlEscape().convert(base64Str);
|
||||
|
||||
try {
|
||||
String baseUrl = url == null ? Config.instance.apiURL : url;
|
||||
log.info("Path:$baseUrl$path");
|
||||
HttpClient client = new HttpClient();
|
||||
var _downloadData = StringBuffer();
|
||||
var fileSave = new File(filePath);
|
||||
var request = await client.getUrl(Uri.parse("$baseUrl$path"));
|
||||
request.headers.set("Project-ID", Config.instance.reportProjectID);
|
||||
request.headers
|
||||
.set(HttpHeaders.contentTypeHeader, "application/json; charset=UTF-8");
|
||||
if (token != null) {
|
||||
request.headers.set("Token", token);
|
||||
}
|
||||
if (androidInfo.androidId != null) {
|
||||
request.headers.set("Device", androidInfo.androidId + ":" + deviceName);
|
||||
}
|
||||
request.headers.set("payload", escapePayload);
|
||||
var response = await request.close();
|
||||
print("headers:${response.headers}");
|
||||
response.transform(utf8.decoder).listen((d) => _downloadData.write(d),
|
||||
onDone: () {
|
||||
fileSave.writeAsString(_downloadData.toString());
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("path:$path, api:$e");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// request makes http request
|
||||
// if token is null
|
||||
Future<dynamic> requestDownloadPDFAPI(String path, method,
|
||||
{dynamic payload, String token, String url, String filePath}) async {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
String deviceName = "${androidInfo.model}(${androidInfo.id})";
|
||||
log.info("device:${androidInfo.androidId},deviceName:$deviceName");
|
||||
|
||||
var bytes = utf8.encode(payload);
|
||||
var base64Str = base64.encode(bytes);
|
||||
String escapePayload = HtmlEscape().convert(base64Str);
|
||||
|
||||
try {
|
||||
String baseUrl = url == null ? Config.instance.apiURL : url;
|
||||
log.info("Path:$baseUrl$path");
|
||||
HttpClient client = new HttpClient();
|
||||
// var _downloadData = StringBuffer();
|
||||
var fileSave = new File(filePath);
|
||||
var request = await client.getUrl(Uri.parse("$baseUrl$path"));
|
||||
request.headers.set("Project-ID", Config.instance.reportProjectID);
|
||||
if (token != null) {
|
||||
request.headers.set("Token", token);
|
||||
}
|
||||
if (androidInfo.androidId != null) {
|
||||
request.headers.set("Device", androidInfo.androidId + ":" + deviceName);
|
||||
}
|
||||
request.headers.set("payload", escapePayload);
|
||||
var response = await request.close();
|
||||
print("headers:${response.headers}");
|
||||
var _downloadData = List<int>();
|
||||
|
||||
response.listen((d) => _downloadData.addAll(d), onDone: () {
|
||||
fileSave.writeAsBytes(_downloadData);
|
||||
});
|
||||
// response.transform(utf8.decoder).listen((d) => _downloadData.write(d),
|
||||
// onDone: () {
|
||||
// fileSave.writeAsString(_downloadData.toString());
|
||||
// });
|
||||
} catch (e) {
|
||||
log.warning("path:$path, api:$e");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
36
lib/model/base_model.dart
Normal file
36
lib/model/base_model.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:fcs/model/api_helper.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
|
||||
import '../vo/setting.dart';
|
||||
import '../vo/user.dart';
|
||||
|
||||
abstract class BaseModel extends ChangeNotifier {
|
||||
User user;
|
||||
Setting setting;
|
||||
MainModel mainModel;
|
||||
|
||||
void initUser(User user) async {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
void initSetting(Setting setting) async {
|
||||
this.setting = setting;
|
||||
}
|
||||
|
||||
void logout();
|
||||
|
||||
// request makes http request
|
||||
// if token is null
|
||||
dynamic request(
|
||||
String path,
|
||||
method, {
|
||||
dynamic payload,
|
||||
String token,
|
||||
String url,
|
||||
}) async {
|
||||
mainModel.resetPinTimer();
|
||||
return await requestAPI(path, method,
|
||||
payload: payload, token: token, url: url);
|
||||
}
|
||||
}
|
||||
177
lib/model/buyer_model.dart
Normal file
177
lib/model/buyer_model.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert' show HtmlEscape, base64, utf8;
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/config.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/buyer.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class BuyerModel extends BaseModel {
|
||||
final log = Logger('BuyerModel');
|
||||
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
|
||||
List<Buyer> buyers = [];
|
||||
PopupMenu popupMenu = new PopupMenu(index: 0);
|
||||
PopupMenu sortMenu = new PopupMenu();
|
||||
Buyer searchBuyer;
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
_loadBuyers();
|
||||
}
|
||||
|
||||
Future<void> _loadBuyers() async {
|
||||
if (!user.isOwnerAndAbove() && !user.hasBuyer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
listener = getQuerySnapshotByOrder(
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection",
|
||||
'user_name')
|
||||
.listen((snaps) async {
|
||||
buyers.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
buyers.add(Buyer.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
if (listener != null) await listener.cancel();
|
||||
buyers = [];
|
||||
}
|
||||
|
||||
Future<Buyer> getBuyer(String buyerID) async {
|
||||
var snap = await getDocSnap(
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection", buyerID);
|
||||
return Buyer.fromMap(snap.data, snap.documentID);
|
||||
}
|
||||
|
||||
Future<Buyer> loadBuyerProducts(Buyer buyer, {bool force = false}) async {
|
||||
if (!force && buyer.buyerProducts != null && buyer.buyerProducts.length > 0)
|
||||
return buyer;
|
||||
var snaps = await getSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${buyer.id}/$product_collection");
|
||||
buyer.buyerProducts = snaps.documents
|
||||
.map((s) => BuyerProduct.fromMap(s.data, s.documentID))
|
||||
.toList();
|
||||
return buyer;
|
||||
}
|
||||
|
||||
Future<void> delete(Buyer buyer) async {
|
||||
await request("/buyer/${buyer.id}", "DELETE", token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> approve(Buyer buyer) async {
|
||||
await request("/buyer/approve", "PUT",
|
||||
payload: buyer.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> reject(Buyer buyer) async {
|
||||
await request("/buyer/reject", "POST",
|
||||
payload: buyer.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> allocate(Buyer buyer) async {
|
||||
await request("/buyer/allocate", "POST",
|
||||
payload: buyer.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
void filterStatus(String status, int _selectedIndex, int _sleectedSortIndex) {
|
||||
this.sortMenu.index = _sleectedSortIndex;
|
||||
buyers.clear();
|
||||
if (listener != null) {
|
||||
listener.cancel();
|
||||
}
|
||||
|
||||
this.popupMenu.index = _selectedIndex;
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$buyer_collection";
|
||||
listener = getFilterStatusSnapshot(path, status, 'user_name')
|
||||
.listen((snaps) async {
|
||||
buyers.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
buyers.add(Buyer.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<Buyer>> search(String searchBuyer) async {
|
||||
if (searchBuyer == null || searchBuyer == '') return List();
|
||||
|
||||
var bytes = utf8.encode(searchBuyer);
|
||||
var base64Str = base64.encode(bytes);
|
||||
HtmlEscape htmlEscape = const HtmlEscape();
|
||||
String escapeBuyer = htmlEscape.convert(base64Str);
|
||||
|
||||
int limit = 20;
|
||||
List<Buyer> _buyers = [];
|
||||
|
||||
try {
|
||||
var data = await request(
|
||||
"/api/fts/$buyer_collection/$escapeBuyer/$limit", "GET",
|
||||
token: await getToken(), url: Config.instance.reportURL);
|
||||
if (data == null) return List();
|
||||
|
||||
data.forEach((buyer) {
|
||||
var _buyer = Buyer.fromJson(buyer);
|
||||
_buyers.add(_buyer);
|
||||
});
|
||||
} catch (e) {
|
||||
// permission error
|
||||
log.warning("buyer error:" + e.toString());
|
||||
return null;
|
||||
}
|
||||
return _buyers;
|
||||
}
|
||||
|
||||
void filterSorting(int _sleectedSortIndex, int _selectedIndex) {
|
||||
this.popupMenu.index = _selectedIndex;
|
||||
buyers.clear();
|
||||
if (listener != null) {
|
||||
listener.cancel();
|
||||
}
|
||||
String _fieldName;
|
||||
bool descending = false;
|
||||
|
||||
if (_sleectedSortIndex == 0) {
|
||||
_fieldName = 'user_name';
|
||||
descending = false;
|
||||
}
|
||||
if (_sleectedSortIndex == 1) {
|
||||
_fieldName = 'user_name';
|
||||
descending = true;
|
||||
}
|
||||
if (_sleectedSortIndex == 2) {
|
||||
_fieldName = 'phone_number';
|
||||
descending = false;
|
||||
}
|
||||
if (_sleectedSortIndex == 3) {
|
||||
_fieldName = 'phone_number';
|
||||
descending = true;
|
||||
}
|
||||
|
||||
this.sortMenu.index = _sleectedSortIndex;
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$buyer_collection";
|
||||
listener =
|
||||
getFilterSnapshot(path, descending, _fieldName).listen((snaps) async {
|
||||
buyers.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
buyers.add(Buyer.fromMap(d.data, d.documentID));
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
notifyListeners();
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
244
lib/model/chart_model.dart
Normal file
244
lib/model/chart_model.dart
Normal file
@@ -0,0 +1,244 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/config.dart';
|
||||
import 'package:fcs/model/firebase_helper.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/vo/po_do_count.dart';
|
||||
import 'package:fcs/vo/revenue.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class ChartModel extends BaseModel {
|
||||
final log = Logger('ChartModel');
|
||||
|
||||
List<POChartData> chartSummary = [];
|
||||
|
||||
Revenue revenue = new Revenue();
|
||||
PODOCount podoCount = new PODOCount();
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
revenue = new Revenue();
|
||||
podoCount = new PODOCount();
|
||||
|
||||
_loadRev();
|
||||
_loadPODOCount();
|
||||
if (user.hasPO() || user.isOwnerAndAbove()) {
|
||||
_getSummary();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadRev() async {
|
||||
try {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$report_collection";
|
||||
String reportID = "revenue";
|
||||
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$report_collection";
|
||||
reportID = "spending";
|
||||
}
|
||||
|
||||
getDocSnapshot(path, reportID).listen((DocumentSnapshot snapshot) async {
|
||||
if (snapshot.data == null) {
|
||||
return;
|
||||
}
|
||||
revenue = Revenue.fromMap(snapshot.data, snapshot.documentID);
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadPODOCount() async {
|
||||
try {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$report_collection";
|
||||
String reportID = "po_do_count";
|
||||
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$report_collection";
|
||||
}
|
||||
|
||||
getDocSnapshot(path, reportID).listen((DocumentSnapshot snapshot) async {
|
||||
if (snapshot.data == null) {
|
||||
return;
|
||||
}
|
||||
podoCount = PODOCount.fromMap(snapshot.data, snapshot.documentID);
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
chartSummary = [];
|
||||
revenue = new Revenue();
|
||||
podoCount = new PODOCount();
|
||||
}
|
||||
|
||||
_getSummary() async {
|
||||
chartSummary.clear();
|
||||
try {
|
||||
var data = {
|
||||
"fields": 'quantity,product_id,product_name,color',
|
||||
"aggfuns": "sum,,,",
|
||||
"groupbys": 'product_id',
|
||||
};
|
||||
var result = await request("/api/data/po_product_view", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
if (result == null) return;
|
||||
chartSummary.clear();
|
||||
result.forEach((chart) {
|
||||
var _list = POChartData.fromJson(chart, "quantity_sum");
|
||||
chartSummary.add(_list);
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error get Summary>>>>${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<POChartData>> loadSummary() async {
|
||||
List<POChartData> _list = List();
|
||||
try {
|
||||
var data = {
|
||||
"fields": 'quantity,product_id,product_name,color',
|
||||
"aggfuns": "sum,,,",
|
||||
"groupbys": 'product_id',
|
||||
};
|
||||
var result = await request("/api/data/po_product_view", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
if (result != null) {
|
||||
result.forEach((chart) {
|
||||
var _data = POChartData.fromJson(chart, "quantity_sum");
|
||||
_list.add(_data);
|
||||
});
|
||||
}
|
||||
return _list;
|
||||
} catch (e) {
|
||||
log.warning("Error>>>>${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<POChartData>> loadUsers() async {
|
||||
List<POChartData> _list = List();
|
||||
var data = {
|
||||
"fields": 'quantity,user_name,product_name',
|
||||
"aggfuns": "sum,,",
|
||||
"groupbys": 'product_id,user_id',
|
||||
};
|
||||
try {
|
||||
var result = await request("/api/data/po_product_view", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
|
||||
result.forEach((chart) {
|
||||
var _buyer = POChartData.fromJson(chart, "quantity_sum");
|
||||
_list.add(_buyer);
|
||||
});
|
||||
return _list;
|
||||
} catch (e) {
|
||||
log.warning("Error load>>>>${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<POBuyerData>> loadPOBalancesForBuyer_() async {
|
||||
List<POBuyerData> _list = List();
|
||||
try {
|
||||
var data = {
|
||||
"fields": 'amount,status',
|
||||
"aggfuns": "sum,",
|
||||
"groupbys": 'status',
|
||||
"filters": [
|
||||
{"field": "user_id", "compare": "==", "value": user.docID}
|
||||
]
|
||||
};
|
||||
var result = await request("/api/data/po_buyer_view", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
|
||||
if (result != null) {
|
||||
result.forEach((chart) {
|
||||
var _data = POBuyerData.fromJson(chart, "amount_sum");
|
||||
_list.add(_data);
|
||||
});
|
||||
}
|
||||
return _list;
|
||||
} catch (e) {
|
||||
log.warning("Error>>>>${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<POChartData>> loadPOBalancesForBuyer() async {
|
||||
List<POChartData> _list = List();
|
||||
try {
|
||||
var data = {
|
||||
"fields": 'quantity,product_id,product_name,user_id,color',
|
||||
"aggfuns": "sum,,,,",
|
||||
"groupbys": 'product_id,user_id',
|
||||
"filters": [
|
||||
{"field": "user_id", "compare": "==", "value": user.docID}
|
||||
]
|
||||
};
|
||||
var result = await request("/api/data/po_product_view", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
if (result != null) {
|
||||
result.forEach((chart) {
|
||||
var _data = POChartData.fromJson(chart, "quantity_sum");
|
||||
_list.add(_data);
|
||||
});
|
||||
}
|
||||
return _list;
|
||||
} catch (e) {
|
||||
log.warning("Error>>>>${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<POChartData>> loadPOBalProductsForBuyer() async {
|
||||
List<POChartData> _list = List();
|
||||
var data = {
|
||||
"fields": 'quantity,user_name,user_id,product_name',
|
||||
"aggfuns": "sum,,,",
|
||||
"groupbys": 'product_id,user_id',
|
||||
"filters": [
|
||||
{"field": "user_id", "compare": "==", "value": user.docID}
|
||||
]
|
||||
};
|
||||
try {
|
||||
var result = await request("/api/data/po_product_view", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
result.forEach((chart) {
|
||||
var _buyer = POChartData.fromJson(chart, "quantity_sum");
|
||||
_list.add(_buyer);
|
||||
});
|
||||
return _list;
|
||||
} catch (e) {
|
||||
log.warning("Error load>>>>${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
lib/model/constants.dart
Normal file
37
lib/model/constants.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
const ok_doc_id = "ok";
|
||||
const setting_doc_id = "ok_setting";
|
||||
|
||||
const config_collection = "configs";
|
||||
const biz_collection = "bizs";
|
||||
const product_collection = "products";
|
||||
const user_collection = "users";
|
||||
const privilege_collection = "privileges";
|
||||
const user_level_collection = "user_levels";
|
||||
const storage_collection = "storages";
|
||||
const buyer_collection = "buyers";
|
||||
const buying_pos = "buying_pos";
|
||||
const selling_pos = "selling_pos";
|
||||
const inventory_takings = "inventory_takings";
|
||||
const inventory_lines = "inventory_lines";
|
||||
const pds_collection = "pds";
|
||||
const pos_collection = "pos";
|
||||
const dos_collection = "dos";
|
||||
const notification_collection = "notifications";
|
||||
const log_collection = "logs";
|
||||
const report_collection = "reports";
|
||||
const po_product_collection = "po_products";
|
||||
const device_collection = "devices";
|
||||
const do_po_lines_collection = "do_po_lines";
|
||||
const reports_collection = "reports";
|
||||
const announcement_collection = "announcements";
|
||||
const report_user_collection = "report_users";
|
||||
|
||||
const po_files_path = "/ok/po";
|
||||
const reg_files_path = "/ok/reg";
|
||||
const do_files_path = "/ok/do";
|
||||
const sign_files_path = "/ok/sign";
|
||||
const bank_images_path = "/ok/banks";
|
||||
|
||||
const po_approved_status = "approved";
|
||||
const po_closed_status = "closed";
|
||||
const do_approved_status = "approved";
|
||||
140
lib/model/delivery_model.dart
Normal file
140
lib/model/delivery_model.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
import 'package:fcs/model/constants.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class DeliveryModel extends BaseModel {
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
|
||||
List<DOSubmission> dos = [];
|
||||
PopupMenu popupMenu = new PopupMenu(index: 0);
|
||||
int dateIndex = 0;
|
||||
DateTime selectedDate = DateTime.now();
|
||||
int timber = 0;
|
||||
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
_loadDOs();
|
||||
}
|
||||
|
||||
Future<void> _loadDOs() async {
|
||||
if (!user.isOwnerAndAbove() && !user.hasDelivery()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
|
||||
var startDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0);
|
||||
var endDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 23, 59, 59);
|
||||
|
||||
listener = getDeliverySnapshot(
|
||||
path, 'delivery_date', startDate, endDate, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
if (listener != null) await listener.cancel();
|
||||
dos = [];
|
||||
}
|
||||
|
||||
Future<DOSubmission> getDO(String id) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
var doSnap = await getDocSnap(path, id);
|
||||
return DOSubmission.fromMap(doSnap.data, doSnap.documentID);
|
||||
}
|
||||
|
||||
Future<void> endDelivery(DOSubmission doObj, Uint8List img) async {
|
||||
String path = Path.join(do_files_path, user.docID);
|
||||
String imgUrl = await uploadStorageData(path, img, fileName: doObj.id);
|
||||
doObj.doReceiptUrl = imgUrl;
|
||||
await request("/do/ended", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
void filterData(
|
||||
String status, DateTime dateTime, int _selectedIndex, int _dateIndex) {
|
||||
dos.clear();
|
||||
|
||||
var startDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 0, 0, 0);
|
||||
var endDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59);
|
||||
|
||||
if (listener != null) {
|
||||
listener.cancel();
|
||||
}
|
||||
|
||||
this.popupMenu.index = _selectedIndex;
|
||||
this.dateIndex = _dateIndex;
|
||||
this.selectedDate = dateTime == null
|
||||
? new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0)
|
||||
: dateTime;
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
|
||||
if (status != null && dateTime == null) {
|
||||
listener = getDeliveryStatusSnapshot(path, status, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else if (dateTime != null && status == null) {
|
||||
listener = getDeliveryDateSnapshot(
|
||||
path, 'delivery_date', startDate, endDate, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else if (status != null && dateTime != null) {
|
||||
listener = getDeliveryDataSnapshot(
|
||||
path, status, 'delivery_date', startDate, endDate, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else {
|
||||
listener =
|
||||
getQuerySnapshotByOrder(path, 'do_number').listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
76
lib/model/device_model.dart
Normal file
76
lib/model/device_model.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:fcs/vo/device.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class PhoneDeviceModel extends BaseModel {
|
||||
List<PhoneDevice> devices = new List();
|
||||
|
||||
bool isLogout = false;
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
_loadDevices();
|
||||
}
|
||||
|
||||
Future<void> _loadDevices() async {
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(
|
||||
"/$biz_collection/${setting.okEnergyId}/$user_collection/${user.docID}/$device_collection")
|
||||
.where("primary_device", isEqualTo: false)
|
||||
.snapshots();
|
||||
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
devices = snaps.documents.map((documentSnapshot) {
|
||||
var data = PhoneDevice.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
if (!data.deviceOn &&
|
||||
!data.primaryDevice &&
|
||||
data.id == androidInfo.androidId &&
|
||||
!documentSnapshot.metadata.isFromCache) {
|
||||
this.isLogout = true;
|
||||
this.mainModel.logout();
|
||||
notifyListeners();
|
||||
}
|
||||
return data;
|
||||
}).toList();
|
||||
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
devices = [];
|
||||
}
|
||||
|
||||
bool isLogoutDevice() {
|
||||
return this.isLogout;
|
||||
}
|
||||
|
||||
setDevice(bool status) {
|
||||
this.isLogout = status;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> confirmDevice(String id, String deviceID) async {
|
||||
await request("/dev/on", "POST",
|
||||
payload: {"id": id, "device_id": deviceID}, token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> logoutDevice(String id, String deviceID) async {
|
||||
await request("/dev/off", "POST",
|
||||
payload: {"id": id, "device_id": deviceID}, token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> setPrimaryDevice(String id, String deviceID) async {
|
||||
await request("/dev", "PUT",
|
||||
payload: {"id": id, "device_id": deviceID}, token: await getToken());
|
||||
}
|
||||
}
|
||||
258
lib/model/do_model.dart
Normal file
258
lib/model/do_model.dart
Normal file
@@ -0,0 +1,258 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
import 'package:fcs/model/api_helper.dart';
|
||||
import 'package:fcs/model/constants.dart';
|
||||
import 'package:fcs/pages/do/do_files.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class DOModel extends BaseModel {
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
|
||||
List<DOSubmission> dos = [];
|
||||
PopupMenu popupMenu = new PopupMenu(index: 0);
|
||||
int dateIndex = 0;
|
||||
DateTime selectedDate = DateTime.now();
|
||||
int timber = 0;
|
||||
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
_loadDOs();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
if (listener != null) await listener.cancel();
|
||||
dos = [];
|
||||
}
|
||||
|
||||
Future<void> _loadDOs() async {
|
||||
String path;
|
||||
if (user.hasDO() || user.isOwnerAndAbove()) {
|
||||
path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
}
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
var startDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0);
|
||||
var endDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 23, 59, 59);
|
||||
|
||||
listener =
|
||||
getFilterDateSnapshot(path, 'do_date', startDate, endDate, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<DOSubmission> loadDOPOLines(DOSubmission doSub) async {
|
||||
String path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$dos_collection/${doSub.id}/$do_po_lines_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection/${doSub.id}/$do_po_lines_collection";
|
||||
}
|
||||
var snaps = await getSnapshot(path);
|
||||
doSub.dopoLies =
|
||||
snaps.documents.map((s) => DOPOLine.fromMap(s.data)).toList();
|
||||
return doSub;
|
||||
}
|
||||
|
||||
Future<DOSubmission> getDO(String id) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
var doSnap = await getDocSnap(path, id);
|
||||
return DOSubmission.fromMap(doSnap.data, doSnap.documentID);
|
||||
}
|
||||
|
||||
Future<void> createDO(DOSubmission doSubmission, DOFiles files) async {
|
||||
doSubmission.userID = user.docID;
|
||||
String path = Path.join(do_files_path, user.docID);
|
||||
|
||||
if (files.storageChargeFile != null) {
|
||||
String url = await uploadStorage(path, files.storageChargeFile);
|
||||
doSubmission.storageReceiptUrl = url;
|
||||
}
|
||||
if (files.licenseFile != null) {
|
||||
String url = await uploadStorage(path, files.licenseFile);
|
||||
doSubmission.driverLicenceUrl = url;
|
||||
}
|
||||
|
||||
await request("/do", "POST",
|
||||
payload: doSubmission.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateDO(DOSubmission doSubmission, DOFiles files) async {
|
||||
String path = Path.join(do_files_path, user.docID);
|
||||
if (files.storageFileChanged) {
|
||||
if (doSubmission.storageReceiptUrl != null &&
|
||||
doSubmission.storageReceiptUrl != '') {
|
||||
await deleteStorageFromUrl(doSubmission.storageReceiptUrl);
|
||||
}
|
||||
doSubmission.storageReceiptUrl = null;
|
||||
String url = await uploadStorage(path, files.storageChargeFile);
|
||||
doSubmission.storageReceiptUrl = url;
|
||||
}
|
||||
|
||||
if (files.licenseFileChanged) {
|
||||
if (doSubmission.driverLicenceUrl != null &&
|
||||
doSubmission.driverLicenceUrl != '') {
|
||||
await deleteStorageFromUrl(doSubmission.driverLicenceUrl);
|
||||
}
|
||||
doSubmission.driverLicenceUrl = null;
|
||||
String url = await uploadStorage(path, files.licenseFile);
|
||||
doSubmission.driverLicenceUrl = url;
|
||||
}
|
||||
await request("/do", "PUT",
|
||||
payload: doSubmission.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> approveDO(DOSubmission doObj) async {
|
||||
await request("/do/approved", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> rejectDO(DOSubmission doObj) async {
|
||||
await request("/do/rejected", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> cancelDO(DOSubmission doObj) async {
|
||||
await request("/do/canceled", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> initDelivery(DOSubmission doObj) async {
|
||||
String path = Path.join(do_files_path, user.docID);
|
||||
String imgUrl =
|
||||
await uploadStorage(path, doObj.driverImg, fileName: doObj.id);
|
||||
doObj.driverImgUrl = imgUrl;
|
||||
await request("/do/initiated", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> startDelivery(DOSubmission doObj) async {
|
||||
await request("/do/started", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> endDelivery(DOSubmission doObj, Uint8List img) async {
|
||||
String path = Path.join(do_files_path, user.docID);
|
||||
String imgUrl = await uploadStorageData(path, img, fileName: doObj.id);
|
||||
doObj.doReceiptUrl = imgUrl;
|
||||
await request("/do/ended", "POST",
|
||||
payload: doObj.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
void filterData(
|
||||
String status, DateTime dateTime, int _selectedIndex, int _dateIndex) {
|
||||
dos.clear();
|
||||
if (listener != null) {
|
||||
listener.cancel();
|
||||
}
|
||||
|
||||
this.popupMenu.index = _selectedIndex;
|
||||
this.dateIndex = _dateIndex;
|
||||
this.selectedDate = dateTime == null
|
||||
? new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0)
|
||||
: dateTime;
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
|
||||
if (status != null && dateTime == null) {
|
||||
listener = getFilterStatusSnapshot(path, status, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else if (dateTime != null && status == null) {
|
||||
var endDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59);
|
||||
listener =
|
||||
getFilterDateSnapshot(path, 'do_date', dateTime, endDate, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else if (status != null && dateTime != null) {
|
||||
var endDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59);
|
||||
listener = getFilterDataSnapshot(
|
||||
path, status, 'do_date', dateTime, endDate, 'do_number')
|
||||
.listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else {
|
||||
listener =
|
||||
getQuerySnapshotByOrder(path, 'do_number').listen((snaps) async {
|
||||
dos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addTimber(int count) {
|
||||
timber = count;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<DOSubmission>> getDOForDelivery(DateTime dateTime) async {
|
||||
List<DOSubmission> dos = [];
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
DateTime date = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||
DateTime dateAddOne = date.add(Duration(days: 1));
|
||||
|
||||
QuerySnapshot snapshots = await Firestore.instance
|
||||
.collection(path)
|
||||
.where("status", isEqualTo: do_approved_status)
|
||||
.where("delivery_date", isGreaterThanOrEqualTo: date)
|
||||
.where("delivery_date", isLessThan: dateAddOne)
|
||||
.orderBy("delivery_date").orderBy("user_name")
|
||||
.limit(100)
|
||||
.getDocuments();
|
||||
snapshots.documents.forEach((d) {
|
||||
dos.add(DOSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
return dos;
|
||||
}
|
||||
}
|
||||
59
lib/model/employee_model.dart
Normal file
59
lib/model/employee_model.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class EmployeeModel extends BaseModel {
|
||||
final log = Logger('EmployeeModel');
|
||||
|
||||
List<User> employees = [];
|
||||
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
_loadEmployees();
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
employees = [];
|
||||
}
|
||||
|
||||
Future<void> _loadEmployees() async {
|
||||
if (!user.isOwnerAndAbove() && !user.hasAccount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Firestore.instance
|
||||
.collection("/$biz_collection/${setting.okEnergyId}/$user_collection")
|
||||
.where("is_employee", isEqualTo: true)
|
||||
.snapshots()
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
employees.clear();
|
||||
employees = snapshot.documents.map((documentSnapshot) {
|
||||
var user =
|
||||
User.fromMap(documentSnapshot.data, documentSnapshot.documentID);
|
||||
return user;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
269
lib/model/firebase_helper.dart
Normal file
269
lib/model/firebase_helper.dart
Normal file
@@ -0,0 +1,269 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_storage/firebase_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final log = Logger('firebaseHelper');
|
||||
|
||||
final FirebaseAuth auth = FirebaseAuth.instance;
|
||||
|
||||
Future<String> getToken() async {
|
||||
FirebaseUser firebaseUser = await auth.currentUser();
|
||||
IdTokenResult token = await firebaseUser.getIdToken();
|
||||
return token.token;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getQuerySnapshot(String path) {
|
||||
log.info("getQuerySnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots =
|
||||
Firestore.instance.collection(path).snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getQuerySnapshotByOrder(String path, String orderName) {
|
||||
log.info("getQuerySnapshotByOrder Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getFilterStatusSnapshot(
|
||||
String path, String searchStatus, String orderName) {
|
||||
log.info("getFilterStatusSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where('status', isEqualTo: searchStatus)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getFilterSnapshot(
|
||||
String path, bool descending, String orderName) {
|
||||
log.info("getFilterSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.orderBy(orderName, descending: descending)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getDeliveryStatusSnapshot(
|
||||
String path, String searchStatus, String orderName) {
|
||||
log.info("getDeliveryStatusSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where('status', isEqualTo: searchStatus)
|
||||
.where("in_delivery", isEqualTo: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getFilterDateSnapshot(String path, String type,
|
||||
DateTime startDate, DateTime endDate, String orderName) {
|
||||
log.info("getFilterDateSnapshot Path: $path");
|
||||
Firestore.instance
|
||||
.collection(path)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.limit(1)
|
||||
.getDocuments()
|
||||
.then((s) {});
|
||||
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getDeliveryDateSnapshot(String path, String type,
|
||||
DateTime startDate, DateTime endDate, String orderName) {
|
||||
log.info("getDeliveryDateSnapshot Path: $path");
|
||||
Firestore.instance
|
||||
.collection(path)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.where("in_delivery", isEqualTo: true)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.limit(1)
|
||||
.getDocuments()
|
||||
.then((s) {});
|
||||
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.where("in_delivery", isEqualTo: true)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getDeliverySnapshot(String path, String type,
|
||||
DateTime startDate, DateTime endDate, String orderName) {
|
||||
log.info("getDeliverySnapshot Path: $path");
|
||||
Firestore.instance
|
||||
.collection(path)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.where("in_delivery", isEqualTo: true)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.limit(1)
|
||||
.getDocuments()
|
||||
.then((s) {});
|
||||
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.where("in_delivery", isEqualTo: true)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getFilterDataSnapshot(String path, String status,
|
||||
String type, DateTime startDate, DateTime endDate, String orderName) {
|
||||
log.info("getFilterDateSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where('status', isEqualTo: status)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getDeliveryDataSnapshot(String path, String status,
|
||||
String type, DateTime startDate, DateTime endDate, String orderName) {
|
||||
log.info("getDeliveryDataSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where('status', isEqualTo: status)
|
||||
.where("in_delivery", isEqualTo: true)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.orderBy(type, descending: true)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getsearchBuyerSnapshot(String path, String buyer) {
|
||||
log.info("getFilterDateSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where("user_name", isEqualTo: buyer)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<DocumentSnapshot> getDocSnapshot(String path, String id) {
|
||||
log.info("getDocSnapshot Path: $path, ID: $id");
|
||||
Stream<DocumentSnapshot> snapshot =
|
||||
Firestore.instance.collection(path).document(id).snapshots();
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getQuerySnapshotF(String path, accountID) {
|
||||
log.info("getQuerySnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where("account_id", isEqualTo: accountID)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> getFilterDateSnapshotF(String path, String accountID,
|
||||
String type, DateTime startDate, DateTime endDate, String orderName) {
|
||||
log.info("getFilterDateSnapshot Path: $path");
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where("account_id", isEqualTo: accountID)
|
||||
.where(type, isGreaterThanOrEqualTo: startDate)
|
||||
.where(type, isLessThanOrEqualTo: endDate)
|
||||
.orderBy(orderName, descending: true)
|
||||
.snapshots();
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Future<QuerySnapshot> getSnapshot(String path) {
|
||||
log.info("getSnapshot Path: $path");
|
||||
Future<QuerySnapshot> snapshots =
|
||||
Firestore.instance.collection(path).getDocuments();
|
||||
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
Future<DocumentSnapshot> getDocSnap(String path, String id) {
|
||||
log.info("getDocSnap Path: $path");
|
||||
return Firestore.instance.collection(path).document(id).get();
|
||||
}
|
||||
|
||||
Future<String> uploadStorage(String path, File file, {String fileName}) async {
|
||||
if (fileName == null) {
|
||||
fileName = Uuid().v4();
|
||||
}
|
||||
StorageReference storageReference =
|
||||
FirebaseStorage.instance.ref().child('$path/$fileName');
|
||||
StorageUploadTask uploadTask = storageReference.putFile(file);
|
||||
await uploadTask.onComplete;
|
||||
String downloadUrl = await storageReference.getDownloadURL();
|
||||
print("name:${await storageReference.getName()}");
|
||||
print("bucket:${await storageReference.getBucket()}");
|
||||
print("path:${await storageReference.getPath()}");
|
||||
print("meta:${await storageReference.getMetadata()}");
|
||||
return downloadUrl;
|
||||
}
|
||||
|
||||
Future<String> uploadStorageData(String path, Uint8List data,
|
||||
{String fileName}) async {
|
||||
if (fileName == null) {
|
||||
fileName = Uuid().v4();
|
||||
}
|
||||
StorageReference storageReference =
|
||||
FirebaseStorage.instance.ref().child('$path/$fileName');
|
||||
StorageUploadTask uploadTask = storageReference.putData(data);
|
||||
await uploadTask.onComplete;
|
||||
String downloadUrl = await storageReference.getDownloadURL();
|
||||
return downloadUrl;
|
||||
}
|
||||
|
||||
Future<void> deleteStorage(String path, name) async {
|
||||
try {
|
||||
StorageReference storageReference =
|
||||
FirebaseStorage.instance.ref().child('$path/$name');
|
||||
await storageReference.delete();
|
||||
} catch (e) {
|
||||
log.warning("deleteStorage:$e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteStorageFromUrl(String url) async {
|
||||
try {
|
||||
StorageReference storageReference =
|
||||
await FirebaseStorage.instance.getReferenceFromUrl(url);
|
||||
await storageReference.delete();
|
||||
} catch (e) {
|
||||
log.warning("deleteStorage:$e");
|
||||
}
|
||||
}
|
||||
45
lib/model/language_model.dart
Normal file
45
lib/model/language_model.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:fcs/model/base_model.dart';
|
||||
import 'package:fcs/model/shared_pref.dart';
|
||||
import 'package:fcs/vo/setting.dart';
|
||||
import 'package:fcs/widget/localization/transalation.dart';
|
||||
|
||||
class LanguageModel extends BaseModel {
|
||||
String language;
|
||||
bool isEng = true;
|
||||
static final List<String> languageCodesList =
|
||||
Translation().supportedLanguagesCodes;
|
||||
static final List<String> languagesList = Translation().supportedLanguages;
|
||||
|
||||
final Map<dynamic, dynamic> languagesMap = {
|
||||
languagesList[0]: languageCodesList[0],
|
||||
languagesList[1]: languageCodesList[1],
|
||||
};
|
||||
|
||||
void initSetting(Setting setting) async {
|
||||
this.language = await load();
|
||||
this.isEng = this.language == "English";
|
||||
Translation().onLocaleChanged(Locale(languagesMap[language]));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
}
|
||||
|
||||
Future<String> load() async {
|
||||
var data =await SharedPref.getLang();
|
||||
if (data == null) return languagesList[1];
|
||||
return data;
|
||||
}
|
||||
|
||||
void saveLanguage(String language) async {
|
||||
Translation().onLocaleChanged(Locale(languagesMap[language]));
|
||||
|
||||
SharedPref.saveLang(language);
|
||||
this.language = language;
|
||||
this.isEng = this.language == "English";
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
59
lib/model/log_model.dart
Normal file
59
lib/model/log_model.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fcs/vo/document_log.dart';
|
||||
import 'package:fcs/vo/log.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
|
||||
class LogModel extends BaseModel {
|
||||
List<Log> logs = [];
|
||||
List<DocLog> docLogs = [];
|
||||
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
_loadLogs();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
logs = [];
|
||||
docLogs=[];
|
||||
}
|
||||
Future<void> _loadLogs() async {
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(
|
||||
"/$biz_collection/${setting.okEnergyId}/$user_collection/${user.docID}/$log_collection")
|
||||
.orderBy("active_time", descending: true)
|
||||
.limit(30)
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
logs = snaps.documents
|
||||
.map((documentSnapshot) =>
|
||||
Log.fromMap(documentSnapshot.data, documentSnapshot.documentID))
|
||||
.toList();
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> loadDocLogs(String docID) async {
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection("/$biz_collection/${setting.okEnergyId}/$log_collection")
|
||||
.where('doc_id', isEqualTo: docID)
|
||||
.orderBy('date', descending: true)
|
||||
.limit(30)
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
docLogs.clear();
|
||||
docLogs = snaps.documents
|
||||
.map((documentSnapshot) => DocLog.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID))
|
||||
.toList();
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
450
lib/model/main_model.dart
Normal file
450
lib/model/main_model.dart
Normal file
@@ -0,0 +1,450 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
import 'package:fcs/model/shared_pref.dart';
|
||||
import 'package:fcs/util.dart';
|
||||
import 'package:fcs/vo/bank_account.dart';
|
||||
import 'package:fcs/vo/buyer.dart';
|
||||
import 'package:fcs/vo/setting.dart';
|
||||
import 'package:fcs/widget/NetworkConnectivity.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import '../vo/status.dart';
|
||||
import '../vo/user.dart';
|
||||
import 'api_helper.dart';
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class ImplementInterfaceModel {
|
||||
void initUser(User user) => {};
|
||||
void initSetting(Setting setting) => {};
|
||||
}
|
||||
|
||||
class MainModel extends ChangeNotifier {
|
||||
final log = Logger('MainModel');
|
||||
|
||||
final FirebaseAuth auth = FirebaseAuth.instance;
|
||||
FirebaseMessaging firebaseMessaging;
|
||||
|
||||
List<BaseModel> models = [];
|
||||
User user;
|
||||
Buyer buyer;
|
||||
FirebaseUser firebaseUser;
|
||||
StreamSubscription<DocumentSnapshot> userListener;
|
||||
StreamSubscription<DocumentSnapshot> buyerListener;
|
||||
bool pinRequired;
|
||||
Timer pinTimer;
|
||||
|
||||
Setting setting;
|
||||
PackageInfo packageInfo;
|
||||
bool isLoaded = true;
|
||||
bool isOnline = true;
|
||||
|
||||
static const PIN_TIME_MIN = 10;
|
||||
|
||||
MainModel() {
|
||||
// NetworkConnectivity.instance.statusStream.listen((data) {
|
||||
// bool _isOnline = data["isOnline"];
|
||||
// if (_isOnline && !this.isOnline) {
|
||||
// init();
|
||||
// }
|
||||
// this.isOnline = _isOnline;
|
||||
// notifyListeners();
|
||||
// });
|
||||
}
|
||||
|
||||
resetPinTimer() {
|
||||
if (pinTimer != null && pinTimer.isActive) {
|
||||
pinTimer.cancel();
|
||||
}
|
||||
pinRequired = false;
|
||||
pinTimer = Timer(Duration(minutes: PIN_TIME_MIN), () {
|
||||
pinRequired = true;
|
||||
});
|
||||
}
|
||||
|
||||
bool isLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasEmail() {
|
||||
return this.user != null && this.user.isEmail();
|
||||
}
|
||||
|
||||
bool agreedTerm() {
|
||||
return this.user != null && this.user.agreeTerms;
|
||||
}
|
||||
|
||||
bool isBuyer() {
|
||||
return this.user == null || this.user.isBuyer();
|
||||
}
|
||||
|
||||
bool isRegBuyer() {
|
||||
return isBuyer() && buyer != null;
|
||||
}
|
||||
|
||||
bool isApprovedBuyer() {
|
||||
return isBuyer() && buyer != null && buyer.isApproved();
|
||||
}
|
||||
|
||||
bool isSysAdmin() {
|
||||
return this.user != null && this.user.isSysAdmin();
|
||||
}
|
||||
|
||||
bool isSysSupport() {
|
||||
return this.user != null && this.user.isSysSupport();
|
||||
}
|
||||
|
||||
bool isBizAdmin() {
|
||||
return this.user != null && this.user.isBizAdmin();
|
||||
}
|
||||
|
||||
bool isOwnerAndAbove() {
|
||||
return this.user != null && this.user.isOwnerAndAbove();
|
||||
}
|
||||
|
||||
bool isAdmin() {
|
||||
return this.user != null && this.user.hasAdmin();
|
||||
}
|
||||
|
||||
bool showHistoryBtn() {
|
||||
return isSysAdmin() || isSysSupport() || isBizAdmin();
|
||||
}
|
||||
|
||||
init() async {
|
||||
// await _loadSetting();
|
||||
// _loadUser();
|
||||
// resetPinTimer();
|
||||
}
|
||||
|
||||
void addModel(BaseModel model) {
|
||||
models.add(model);
|
||||
model.mainModel = this;
|
||||
}
|
||||
|
||||
void _initUser(User user) {
|
||||
models.forEach((m) => m.initUser(user));
|
||||
|
||||
if (firebaseMessaging != null) {
|
||||
firebaseMessaging.subscribeToTopic(user.docID);
|
||||
}
|
||||
}
|
||||
|
||||
void _initSetting(Setting setting) {
|
||||
models.forEach((m) => m.initSetting(setting));
|
||||
}
|
||||
|
||||
Future<void> _loadSetting() async {
|
||||
this.setting = await _getSetting();
|
||||
this.packageInfo = await PackageInfo.fromPlatform();
|
||||
_initSetting(setting);
|
||||
}
|
||||
|
||||
void _loadUser() async {
|
||||
this.firebaseUser = await auth.currentUser();
|
||||
if (this.firebaseUser == null) {
|
||||
this.isLoaded = true;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
_logUser(this.firebaseUser);
|
||||
|
||||
// load from local, if successful,notify listeners
|
||||
User _user = await SharedPref.getUser();
|
||||
if (_user != null) {
|
||||
await _user.setFirebaseUser(this.firebaseUser);
|
||||
_initUser(_user);
|
||||
this.user = _user;
|
||||
|
||||
if (this.user.isRegisteredBuyer()) {
|
||||
_loadBuyer();
|
||||
}
|
||||
this.isLoaded = true;
|
||||
notifyListeners();
|
||||
log.info("user loaded from shared pref!");
|
||||
}
|
||||
|
||||
_listenUser();
|
||||
}
|
||||
|
||||
void _listenUser() {
|
||||
if (this.userListener != null) {
|
||||
this.userListener.cancel();
|
||||
}
|
||||
|
||||
this.userListener = getDocSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$user_collection",
|
||||
firebaseUser.uid)
|
||||
.listen((userSnap) async {
|
||||
if (userSnap.exists) {
|
||||
User _user = User.fromMap(userSnap.data, userSnap.documentID);
|
||||
|
||||
// load claims
|
||||
try {
|
||||
FirebaseUser _firebaseUser = await getProfile(this.firebaseUser);
|
||||
|
||||
await _user.setFirebaseUser(_firebaseUser);
|
||||
_initUser(_user);
|
||||
this.user = _user;
|
||||
this.firebaseUser = _firebaseUser;
|
||||
await SharedPref.saveUser(this.user);
|
||||
} catch (e) {
|
||||
log.warning(e.toString());
|
||||
}
|
||||
|
||||
log.info(
|
||||
"_loadUser => ID: ${this.user.docID}, AccountID: ${this.user.accountID},"
|
||||
"BizID: ${this.user.accountID},"
|
||||
", Privileges: ${this.user.claimPrivileges}, isSysAdmin: ${this.user.isSysAdmin()}");
|
||||
|
||||
if (this.user.isRegisteredBuyer()) {
|
||||
_loadBuyer();
|
||||
}
|
||||
this.isLoaded = true;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _loadBuyer() async {
|
||||
if (this.user == null) return;
|
||||
if (buyerListener != null) buyerListener.cancel();
|
||||
buyerListener = getDocSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection",
|
||||
this.user.docID)
|
||||
.listen((buyerSnap) async {
|
||||
if (buyerSnap.exists) {
|
||||
this.buyer = Buyer.fromMap(buyerSnap.data, buyerSnap.documentID);
|
||||
} else {
|
||||
this.buyer = null;
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// super.dispose();
|
||||
// if (this.userListener != null) {
|
||||
// this.userListener.cancel();
|
||||
// }
|
||||
// SharedPref.removeUser();
|
||||
// this.user = User();
|
||||
}
|
||||
|
||||
Future<void> login(String phoneNumber, String pass) async {
|
||||
var id = phoneNumber.replaceFirst("+", "");
|
||||
id = updatePhoneNumber(id);
|
||||
|
||||
var data = {"id": id, "password": pass};
|
||||
var result = await requestAPI("/login", "POST", payload: data);
|
||||
|
||||
var token = result["Token"];
|
||||
|
||||
// login with custom token
|
||||
AuthResult r = await this.auth.signInWithCustomToken(token: token);
|
||||
this.firebaseUser = r.user;
|
||||
isLoaded = false;
|
||||
_loadUser();
|
||||
_logUser(this.firebaseUser);
|
||||
}
|
||||
|
||||
Future<FirebaseUser> getProfile(FirebaseUser firebaseUser) async {
|
||||
IdTokenResult idtoken = await firebaseUser.getIdToken();
|
||||
var data = await requestAPI(
|
||||
"/profile",
|
||||
"GET",
|
||||
token: idtoken.token,
|
||||
);
|
||||
var _token = data["Token"];
|
||||
AuthResult a = await this.auth.signInWithCustomToken(token: _token);
|
||||
return a.user;
|
||||
}
|
||||
|
||||
Future<void> _logUser(FirebaseUser firebaseUser) async {
|
||||
IdTokenResult idtoken = await firebaseUser.getIdToken();
|
||||
|
||||
await requestAPI(
|
||||
"/log",
|
||||
"GET",
|
||||
token: idtoken.token,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
if (this.userListener != null) {
|
||||
await this.userListener.cancel();
|
||||
}
|
||||
|
||||
await auth.signOut();
|
||||
this.user = null;
|
||||
this.buyer = null;
|
||||
this.firebaseUser = null;
|
||||
await SharedPref.removeUser();
|
||||
if (firebaseMessaging != null) {
|
||||
firebaseMessaging.unsubscribeFromTopic(user.docID);
|
||||
}
|
||||
|
||||
// logout models
|
||||
models.forEach((m) => m.logout());
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> signup(
|
||||
String name, password, confirmPassword, phoneNumber) async {
|
||||
if (password == "" || password.length < 6) {
|
||||
throw Exception("Password must be at least 6 characters");
|
||||
}
|
||||
if (password != confirmPassword) {
|
||||
throw Exception("Password mismatch");
|
||||
}
|
||||
var id = phoneNumber.replaceFirst("+", "");
|
||||
id = updatePhoneNumber(id);
|
||||
|
||||
var inputData = {"id": id, "password": password, "user_name": name};
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
String deviceName = "${androidInfo.model}(${androidInfo.id})";
|
||||
|
||||
var url = "${Config.instance.apiURL}/signup";
|
||||
Response response = await Dio().post(url,
|
||||
data: inputData,
|
||||
options: Options(
|
||||
headers: {"Device": androidInfo.androidId + ":" + deviceName}));
|
||||
|
||||
var data = Status.fromJson(response.data);
|
||||
if (data.status != 'Ok') {
|
||||
throw Exception("${data.errorCode} : ${data.message}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> confirmSignup(
|
||||
String phoneNumber, password, confirmSMSCode) async {
|
||||
var id = phoneNumber.replaceFirst("+", "");
|
||||
id = updatePhoneNumber(id);
|
||||
|
||||
if (confirmSMSCode == "" || confirmSMSCode.length != 6) {
|
||||
throw Exception("Password must be 6 characters");
|
||||
}
|
||||
|
||||
var inputData = {
|
||||
"id": id,
|
||||
"password": password,
|
||||
"confirmation_code": confirmSMSCode
|
||||
};
|
||||
var url = "${Config.instance.apiURL}/confirm";
|
||||
Response response = await Dio().post(
|
||||
url,
|
||||
data: inputData,
|
||||
);
|
||||
var data = Status.fromJson(response.data);
|
||||
if (data.status != 'Ok') {
|
||||
throw Exception(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
bool isSupport() {
|
||||
if (packageInfo == null || setting == null) return false;
|
||||
return int.parse(packageInfo.buildNumber) >= setting.supportBuildNum;
|
||||
}
|
||||
|
||||
Future<Setting> _getSetting() async {
|
||||
var snap = await Firestore.instance
|
||||
.collection(config_collection)
|
||||
.document(setting_doc_id)
|
||||
.get();
|
||||
if (!snap.exists) {
|
||||
return null;
|
||||
}
|
||||
_listSetting();
|
||||
return Setting.fromMap(snap.data);
|
||||
}
|
||||
|
||||
void _listSetting() {
|
||||
getDocSnapshot("/configs", setting_doc_id).listen((snap) {
|
||||
this.setting = Setting.fromMap(snap.data);
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateProfile(String name) async {
|
||||
await requestAPI("/user", "PUT",
|
||||
payload: {"user_name": name}, token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateTerms(String terms) async {
|
||||
await requestAPI("/terms", "PUT",
|
||||
payload: {"terms": terms}, token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> agreeTerms() async {
|
||||
await requestAPI("/user/agree", "PUT", token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateContact(Setting setting) async {
|
||||
await requestAPI("/contact", "PUT",
|
||||
payload: {
|
||||
'email': setting.email,
|
||||
'facebook_url': setting.facebook,
|
||||
'web_url': setting.website,
|
||||
'phones': setting.phones,
|
||||
'bank_account_info': setting.bankAccountInfo,
|
||||
'delivery_phone': setting.deliveryPhone,
|
||||
'address': setting.address,
|
||||
},
|
||||
token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateSetting(Setting setting) async {
|
||||
await requestAPI("/setting", "PUT",
|
||||
payload: {
|
||||
'do_expire_hours': setting.doExpireInHours,
|
||||
'po_expire_hours': setting.poExpireInHours,
|
||||
'po_open_at': setting.poOpenAt,
|
||||
'po_close_at': setting.poCloseAt,
|
||||
'po_close_on': setting.poCloseOn,
|
||||
'first_storage_charge_in': setting.firstStorageChargeIn,
|
||||
'first_storage_charge': setting.firstStorageCharge,
|
||||
'second_storage_charge_in': setting.secondStorageChargeIn,
|
||||
'second_storage_charge': setting.secondStorageCharge,
|
||||
'latest_delivery_days': setting.latestDeliveryDay,
|
||||
},
|
||||
token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> addBankAccount(BankAccount bankAccount, File image) async {
|
||||
String url = await uploadStorage(bank_images_path, image);
|
||||
bankAccount.bankLogo = url;
|
||||
|
||||
await requestAPI("/bank_accounts", "POST",
|
||||
payload: bankAccount.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateBankAccount(BankAccount bankAccount, File image) async {
|
||||
if (image != null) {
|
||||
String url = await uploadStorage(bank_images_path, image);
|
||||
bankAccount.bankLogo = url;
|
||||
}
|
||||
|
||||
await requestAPI("/bank_accounts", "PUT",
|
||||
payload: bankAccount.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> deleteBankAccount(BankAccount bankAccount) async {
|
||||
await requestAPI("/bank_accounts", "DELETE",
|
||||
payload: bankAccount.toMap(), token: await getToken());
|
||||
}
|
||||
}
|
||||
234
lib/model/manual_model.dart
Normal file
234
lib/model/manual_model.dart
Normal file
@@ -0,0 +1,234 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:fcs/model/firebase_helper.dart';
|
||||
import 'package:fcs/vo/manual.dart';
|
||||
import 'package:fcs/vo/setting.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
|
||||
import 'base_model.dart';
|
||||
|
||||
typedef void SlideDataCallback();
|
||||
|
||||
class ManualModel extends BaseModel {
|
||||
final log = Logger('ManualModel');
|
||||
|
||||
List<ManualItem> helps = [];
|
||||
List<ManualItem> manuals = [];
|
||||
String version;
|
||||
SlideDataCallback slideDataCallback;
|
||||
List deleteImage = [];
|
||||
|
||||
String dataDir;
|
||||
|
||||
void initSetting(Setting setting) async {
|
||||
_download(setting);
|
||||
super.initSetting(setting);
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {}
|
||||
|
||||
Future<void> _download(Setting setting) async {
|
||||
this.dataDir = (await getApplicationDocumentsDirectory()).path;
|
||||
version = setting.helpVersion;
|
||||
var file = File('$dataDir/${setting.helpFileName()}');
|
||||
if (await file.exists()) {
|
||||
_loadJsonData();
|
||||
return;
|
||||
}
|
||||
|
||||
String url = setting.helpURL;
|
||||
var req = await http.Client().get(Uri.parse(url));
|
||||
|
||||
File zippedFile = await file.writeAsBytes(req.bodyBytes);
|
||||
File prev = File('$dataDir/manual');
|
||||
if (await prev.exists()) {
|
||||
await prev.delete(recursive: true);
|
||||
}
|
||||
|
||||
var bytes = zippedFile.readAsBytesSync();
|
||||
var archive = ZipDecoder().decodeBytes(bytes);
|
||||
for (var file in archive) {
|
||||
var fileName = '$dataDir/manual/${file.name}';
|
||||
if (file.isFile) {
|
||||
var outFile = File(fileName);
|
||||
outFile = await outFile.create(recursive: true);
|
||||
await outFile.writeAsBytes(file.content);
|
||||
}
|
||||
}
|
||||
_loadJsonData();
|
||||
}
|
||||
|
||||
List<ManualItem> getHelpList(bool isBuyer) {
|
||||
return helps.where((h) => isBuyer ? h.isBuyer : true).toList();
|
||||
}
|
||||
|
||||
getSlideList(int manIndex) {
|
||||
var slides = helps[manIndex].slides;
|
||||
return slides;
|
||||
}
|
||||
|
||||
getSlideData(int manIndex, int slideIndex) {
|
||||
var slide;
|
||||
slide = helps[manIndex].slides[slideIndex];
|
||||
return slide;
|
||||
}
|
||||
|
||||
Future<void> _loadJsonData() async {
|
||||
try {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
File file = File('${directory.path}/manual/manual.json');
|
||||
String contents = await file.readAsString();
|
||||
|
||||
var convertArray = jsonDecode(contents);
|
||||
manuals.clear();
|
||||
convertArray.forEach((item) {
|
||||
var _list = ManualItem.fromMap(item);
|
||||
manuals.add(_list);
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error:${e.toString()}");
|
||||
}
|
||||
helps.clear();
|
||||
helps = manuals.map((e) => ManualItem.clone(e)).toList();
|
||||
}
|
||||
|
||||
addManualTitle(ManualItem manualItem) {
|
||||
helps.add(manualItem);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
uploadStorageManualData(String version, String dir) async {
|
||||
String fileName = 'help-v$version.zip';
|
||||
|
||||
var converthelps = [];
|
||||
for (final converthelp in helps) {
|
||||
converthelps.add(converthelp.toJson());
|
||||
}
|
||||
var json = jsonEncode(converthelps);
|
||||
|
||||
Directory myDir = new Directory('$dir/manual/img');
|
||||
List _images;
|
||||
_images = myDir.listSync(recursive: true, followLinks: false);
|
||||
|
||||
var newImgData = await convertArchiveImgFile(_images, dir, json);
|
||||
File updateImgFile = File('$dir/manual/update');
|
||||
updateImgFile.writeAsBytesSync(newImgData);
|
||||
|
||||
var bytes = updateImgFile.readAsBytesSync();
|
||||
uploadDataZip(bytes, fileName);
|
||||
}
|
||||
|
||||
convertArchiveImgFile(List imgList, String dataDir, json) async {
|
||||
File file = new File('$dataDir/update');
|
||||
if (await file.exists()) {
|
||||
await file.delete(recursive: true);
|
||||
}
|
||||
|
||||
Archive zipArchive = new Archive();
|
||||
List<int> utf8encoded = utf8.encode(json);
|
||||
ArchiveFile jsonFile =
|
||||
new ArchiveFile("manual.json", utf8encoded.length, utf8encoded);
|
||||
zipArchive.addFile(jsonFile);
|
||||
|
||||
for (var img in imgList) {
|
||||
String basename = Path.basename(img.path);
|
||||
if (deleteImage.length != 0) {
|
||||
for (var dImgName in deleteImage) {
|
||||
if (dImgName != basename) {
|
||||
Uint8List bytesPhoto = img.readAsBytesSync() as Uint8List;
|
||||
ArchiveFile jsonFile =
|
||||
new ArchiveFile("img/$basename", bytesPhoto.length, bytesPhoto);
|
||||
zipArchive.addFile(jsonFile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Uint8List bytesPhoto = img.readAsBytesSync() as Uint8List;
|
||||
ArchiveFile jsonFile =
|
||||
new ArchiveFile("img/$basename", bytesPhoto.length, bytesPhoto);
|
||||
zipArchive.addFile(jsonFile);
|
||||
}
|
||||
}
|
||||
List<int> zipInBytes = new ZipEncoder().encode(zipArchive);
|
||||
file.writeAsBytesSync(zipInBytes);
|
||||
var bytes = file.readAsBytesSync();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
uploadDataZip(Uint8List data, String fileName) async {
|
||||
String path = '/ok/img';
|
||||
String url = await uploadStorageData(path, data, fileName: fileName);
|
||||
}
|
||||
|
||||
resetManualItems() {
|
||||
helps.clear();
|
||||
//clone manauals
|
||||
helps = manuals.map((p) => ManualItem.clone(p)).toList();
|
||||
// return helps.where((h) => isBuyer ? h.isBuyer : true).toList();
|
||||
}
|
||||
|
||||
saveInstruction(int manIndex, int slideIndex, int instIndex,
|
||||
Instruction instruction, Instruction oldInst, bool isEng) {
|
||||
if (isEng) {
|
||||
var inst = helps[manIndex].slides[slideIndex].instructions.toList();
|
||||
instruction.id = oldInst.id;
|
||||
if (inst.length != 0) {
|
||||
helps[manIndex].slides[slideIndex].instructions.remove(oldInst);
|
||||
helps[manIndex]
|
||||
.slides[slideIndex]
|
||||
.instructions
|
||||
.insert(instIndex, instruction);
|
||||
} else {
|
||||
helps[manIndex].slides[slideIndex].instructions.add(instruction);
|
||||
}
|
||||
} else {
|
||||
var inst = helps[manIndex].slides[slideIndex].instructionsmm.toList();
|
||||
instruction.id = oldInst.id;
|
||||
if (inst.length != 0) {
|
||||
helps[manIndex].slides[slideIndex].instructionsmm.remove(oldInst);
|
||||
helps[manIndex]
|
||||
.slides[slideIndex]
|
||||
.instructionsmm
|
||||
.insert(instIndex, instruction);
|
||||
} else {
|
||||
helps[manIndex].slides[slideIndex].instructionsmm.add(instruction);
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
saveSlideData(int manIndex, int slideIndex, SlideData slideData) {
|
||||
helps[manIndex].slides.add(slideData);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
changeSlideImage(int manIndex, int slideIndex, SlideData slideData) {
|
||||
var oldSlide = helps[manIndex].slides[slideIndex];
|
||||
helps[manIndex].slides.remove(oldSlide);
|
||||
helps[manIndex].slides.insert(slideIndex, slideData);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
deleteSlideData(int manIndex, SlideData slideData) {
|
||||
String engImage = slideData.image;
|
||||
String mmImage = slideData.imagemm;
|
||||
deleteImage.add(mmImage);
|
||||
deleteImage.add(engImage);
|
||||
helps[manIndex].slides.remove(slideData);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
deleteManualItem(ManualItem item) {
|
||||
// if(helps.isEmpty){
|
||||
// helps = new List();
|
||||
// }
|
||||
helps.remove(item);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
35
lib/model/messaging.dart
Normal file
35
lib/model/messaging.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
|
||||
class MessagingFCM {
|
||||
final log = Logger('MessagingFCM');
|
||||
|
||||
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||
|
||||
MessagingFCM(User user) {
|
||||
_firebaseMessaging.configure(
|
||||
onMessage: (Map<String, dynamic> message) async {
|
||||
log.info("onMessage: $message");
|
||||
},
|
||||
// onBackgroundMessage: backgroundMessageHandler,
|
||||
onLaunch: (Map<String, dynamic> message) async {
|
||||
log.info("onLaunch: $message");
|
||||
},
|
||||
onResume: (Map<String, dynamic> message) async {
|
||||
log.info("onResume: $message");
|
||||
},
|
||||
);
|
||||
_firebaseMessaging.requestNotificationPermissions(
|
||||
const IosNotificationSettings(
|
||||
sound: true, badge: true, alert: true, provisional: true));
|
||||
_firebaseMessaging.onIosSettingsRegistered
|
||||
.listen((IosNotificationSettings settings) {
|
||||
log.info("Settings registered: $settings");
|
||||
});
|
||||
_firebaseMessaging.getToken().then((String token) {
|
||||
log.info("Token:$token");
|
||||
});
|
||||
_firebaseMessaging.subscribeToTopic(user.docID);
|
||||
}
|
||||
}
|
||||
74
lib/model/notification_model.dart
Normal file
74
lib/model/notification_model.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fcs/vo/notification.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class NotificationModel extends BaseModel {
|
||||
int filer=0;
|
||||
List<Notification> notifications = [];
|
||||
|
||||
var filterValues = {1: "po", 2: "do", 3: "buyer"};
|
||||
List<Notification> get notis {
|
||||
return notifications
|
||||
.where((n) => filer == 0 || n.itemType == filterValues[filer])
|
||||
.toList();
|
||||
}
|
||||
|
||||
int unseen = 0;
|
||||
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
_loadNotifications();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
notifications = [];
|
||||
}
|
||||
|
||||
Future<void> _loadNotifications() async {
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(
|
||||
"/$biz_collection/${setting.okEnergyId}/$user_collection/${user.docID}/$notification_collection")
|
||||
.orderBy("time", descending: true)
|
||||
.limit(50)
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
notifications.clear();
|
||||
unseen = 0;
|
||||
// snaps.documentChanges.forEach((c) {
|
||||
// if (c.type == DocumentChangeType.added) {
|
||||
// FlutterRingtonePlayer.play(
|
||||
// android: AndroidSounds.notification,
|
||||
// ios: IosSounds.glass,
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
snaps.documents.forEach((d) {
|
||||
var n = Notification.fromMap(d.data, d.documentID);
|
||||
if (!n.seen) unseen++;
|
||||
notifications.add(n);
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
void filter(int filter) {
|
||||
this.filer = filter;
|
||||
}
|
||||
|
||||
Future<void> seen() async {
|
||||
await request("/notification/seen", "POST", token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> seenID(String id) async {
|
||||
await request("/notification/seen/${user.docID}/$id", "POST",
|
||||
token: await getToken());
|
||||
}
|
||||
}
|
||||
4
lib/model/pagination/paginator.dart
Normal file
4
lib/model/pagination/paginator.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
class Listener {
|
||||
|
||||
}
|
||||
88
lib/model/pd_model.dart
Normal file
88
lib/model/pd_model.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/model/constants.dart';
|
||||
import 'package:fcs/vo/pd.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class PDModel extends BaseModel {
|
||||
final log = Logger('PDModel');
|
||||
|
||||
List<PD> pds = [];
|
||||
int dateIndex = 0;
|
||||
DateTime selectedDate = DateTime.now();
|
||||
|
||||
void initUser(User user) async {
|
||||
super.initUser(user);
|
||||
loadPDs();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
pds = [];
|
||||
}
|
||||
|
||||
loadPDs() {
|
||||
if (!user.isOwnerAndAbove() && !user.hasInventory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$pds_collection";
|
||||
var startDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0);
|
||||
var endDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 23, 59, 59);
|
||||
pds.clear();
|
||||
getFilterDateSnapshot(path, 'date', startDate, endDate, 'pd_number')
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
pds = snapshot.documents.map((documentSnapshot) {
|
||||
var data =
|
||||
PD.fromMap(documentSnapshot.data, documentSnapshot.documentID);
|
||||
return data;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<PD> loadPDLines(PD pd) async {
|
||||
var snaps = await getSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$pds_collection/${pd.id}/$product_collection");
|
||||
pd.pdLines = snaps.documents.map((s) => PDLine.fromMap(s.data)).toList();
|
||||
return pd;
|
||||
}
|
||||
|
||||
Future<void> createPD(PD pd) async {
|
||||
await request("/pd", "POST",
|
||||
payload: pd.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
void filterDate(DateTime dateTime, int _dateIndex) {
|
||||
this.selectedDate = dateTime;
|
||||
this.dateIndex = _dateIndex;
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$pds_collection";
|
||||
var endDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59);
|
||||
pds.clear();
|
||||
getFilterDateSnapshot(path, 'date', dateTime, endDate, 'pd_number').listen(
|
||||
(snaps) {
|
||||
pds = snaps.documents.map((documentSnapshot) {
|
||||
var data =
|
||||
PD.fromMap(documentSnapshot.data, documentSnapshot.documentID);
|
||||
return data;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
}, onError: (error) {
|
||||
log.warning("FIRESTORE ERROR>>$error");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
294
lib/model/po_model.dart
Normal file
294
lib/model/po_model.dart
Normal file
@@ -0,0 +1,294 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
import 'package:fcs/model/constants.dart';
|
||||
import 'package:fcs/pages/po/po_files.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class POSubmissionModel extends BaseModel {
|
||||
final log = Logger('POSubmissionModel');
|
||||
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
|
||||
List<POSubmission> pos = [];
|
||||
List<POSubmission> approvedPOs = [];
|
||||
PopupMenu popupMenu = new PopupMenu(index: 0);
|
||||
int dateIndex = 0;
|
||||
DateTime selectedDate = DateTime.now();
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
_loadPOs();
|
||||
_loadApprovedPOs();
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
if (listener != null) await listener.cancel();
|
||||
pos = [];
|
||||
approvedPOs = [];
|
||||
}
|
||||
|
||||
Future<void> _loadPOs() async {
|
||||
String path;
|
||||
if (user.hasPO() || user.isOwnerAndAbove()) {
|
||||
path = "/$biz_collection/${setting.okEnergyId}/$pos_collection";
|
||||
}
|
||||
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$pos_collection";
|
||||
}
|
||||
var startDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0);
|
||||
var endDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 23, 59, 59);
|
||||
listener =
|
||||
getFilterDateSnapshot(path, 'po_date', startDate, endDate, 'po_number')
|
||||
.listen((snaps) async {
|
||||
pos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
pos.add(POSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadApprovedPOs() async {
|
||||
if (!user.isBuyer()) {
|
||||
return;
|
||||
}
|
||||
approvedPOs.clear();
|
||||
|
||||
String path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$pos_collection";
|
||||
var docs = await Firestore.instance
|
||||
.collection(path)
|
||||
.where("status", isEqualTo: po_approved_status)
|
||||
.orderBy("po_approved_date", descending: false)
|
||||
.limit(1)
|
||||
.getDocuments();
|
||||
|
||||
Firestore.instance
|
||||
.collection(path)
|
||||
.where("status", isEqualTo: po_approved_status)
|
||||
.orderBy("po_approved_date", descending: false)
|
||||
.limit(10)
|
||||
.snapshots(includeMetadataChanges: true)
|
||||
.listen((snaps) async {
|
||||
List<POSubmission> _approved = [];
|
||||
for (var d in snaps.documents) {
|
||||
if (d.metadata.isFromCache) continue;
|
||||
var po = POSubmission.fromMap(d.data, d.documentID);
|
||||
po.poLines = await loadPOLines(po.id);
|
||||
_approved.add(po);
|
||||
}
|
||||
approvedPOs.clear();
|
||||
approvedPOs.addAll(_approved);
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<POSubmission> getPO(String id) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$pos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$pos_collection";
|
||||
}
|
||||
var poSnap = await getDocSnap(path, id);
|
||||
return POSubmission.fromMap(poSnap.data, poSnap.documentID);
|
||||
}
|
||||
|
||||
Future<List<POLine>> loadPOLines(String poID) async {
|
||||
String path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$pos_collection/$poID/$po_product_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$pos_collection/$poID/$product_collection";
|
||||
}
|
||||
var snaps = await Firestore.instance.collection(path).getDocuments();
|
||||
List<POLine> poLines =
|
||||
snaps.documents.map((s) => POLine.fromMap(s.data)).toList();
|
||||
return poLines;
|
||||
}
|
||||
|
||||
Future<POSubmission> loadDOs(POSubmission po) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$dos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection";
|
||||
}
|
||||
var snaps = await Firestore.instance
|
||||
.collection(path)
|
||||
.where("po_number", isEqualTo: po.poNumber)
|
||||
.orderBy('do_number', descending: true)
|
||||
.getDocuments();
|
||||
|
||||
po.dos = snaps.documents
|
||||
.map((s) => DOSubmission.fromMap(s.data, s.documentID))
|
||||
.toList();
|
||||
return po;
|
||||
}
|
||||
|
||||
Future<DOSubmission> loadDOLines(DOSubmission doSub) async {
|
||||
String path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$dos_collection/${doSub.id}/$product_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$dos_collection/${doSub.id}/$product_collection";
|
||||
}
|
||||
var snaps = await getSnapshot(path);
|
||||
doSub.doLines = snaps.documents.map((s) => DOLine.fromMap(s.data)).toList();
|
||||
return doSub;
|
||||
}
|
||||
|
||||
Future<void> createPO(POSubmission po, List<File> files) async {
|
||||
if (files != null) {
|
||||
if (files.length > 5) throw Exception("Exceed number of file upload");
|
||||
po.poReceiptUrls = [];
|
||||
for (File f in files) {
|
||||
String path = Path.join(po_files_path, user.docID);
|
||||
String url = await uploadStorage(path, f);
|
||||
po.poReceiptUrls.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
await request("/po", "POST", payload: po.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updatePO(
|
||||
POSubmission po, List<File> files, List<String> deletedUrls) async {
|
||||
if (deletedUrls != null)
|
||||
for (String url in deletedUrls) {
|
||||
po.poReceiptUrls.remove(url);
|
||||
await deleteStorageFromUrl(url);
|
||||
}
|
||||
|
||||
if (files != null) {
|
||||
if (files.length + po.poReceiptUrls.length > 5)
|
||||
throw Exception("Exceed number of file upload");
|
||||
po.poReceiptUrls = po.poReceiptUrls == null ? [] : po.poReceiptUrls;
|
||||
for (File f in files) {
|
||||
String path = Path.join(po_files_path, user.docID);
|
||||
String url = await uploadStorage(path, f);
|
||||
po.poReceiptUrls.add(url);
|
||||
}
|
||||
}
|
||||
await request("/po", "PUT", payload: po.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> approvePO(POSubmission po) async {
|
||||
await request("/po/approved", "POST",
|
||||
payload: po.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> rejectPO(POSubmission po) async {
|
||||
await request("/po/rejected", "POST",
|
||||
payload: po.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> cancelPO(POSubmission po) async {
|
||||
await request("/po/canceled", "POST",
|
||||
payload: po.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
void filterData(
|
||||
String status, DateTime dateTime, int _selectedStatus, int _dateIndex) {
|
||||
pos.clear();
|
||||
var startDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 0, 0, 0);
|
||||
var endDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59);
|
||||
|
||||
if (this.listener != null) {
|
||||
this.listener.cancel();
|
||||
}
|
||||
|
||||
this.popupMenu.index = _selectedStatus;
|
||||
this.dateIndex = _dateIndex;
|
||||
this.selectedDate = dateTime == null
|
||||
? new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0)
|
||||
: dateTime;
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$pos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$pos_collection";
|
||||
}
|
||||
|
||||
if (status != null && dateTime == null) {
|
||||
this.listener = getFilterStatusSnapshot(path, status, 'po_number')
|
||||
.listen((snaps) async {
|
||||
pos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
pos.add(POSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else if (dateTime != null && status == null) {
|
||||
this.listener = getFilterDateSnapshot(
|
||||
path, 'po_date', startDate, endDate, 'po_number')
|
||||
.listen((snaps) async {
|
||||
pos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
pos.add(POSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else if (status != null && dateTime != null) {
|
||||
this.listener = getFilterDataSnapshot(
|
||||
path, status, 'po_date', startDate, endDate, 'po_number')
|
||||
.listen((snaps) {
|
||||
pos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
pos.add(POSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} else {
|
||||
this.listener =
|
||||
getQuerySnapshotByOrder(path, 'po_number').listen((snaps) async {
|
||||
pos.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
pos.add(POSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<POSubmission>> getPOForRevenue(DateTime dateTime) async {
|
||||
List<POSubmission> pos = [];
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$pos_collection";
|
||||
if (user.isBuyer()) {
|
||||
path =
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$pos_collection";
|
||||
}
|
||||
DateTime date = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||
DateTime dateAddOne = date.add(Duration(days: 1));
|
||||
|
||||
QuerySnapshot snapshots = await Firestore.instance
|
||||
.collection(path)
|
||||
.where('status', whereIn: [po_approved_status, po_closed_status])
|
||||
.where("po_approved_date", isGreaterThanOrEqualTo: date)
|
||||
.where("po_approved_date", isLessThan: dateAddOne)
|
||||
.orderBy("po_approved_date")
|
||||
.orderBy("user_name")
|
||||
.limit(100)
|
||||
.getDocuments();
|
||||
snapshots.documents.forEach((d) {
|
||||
pos.add(POSubmission.fromMap(d.data, d.documentID));
|
||||
});
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
119
lib/model/product_model.dart
Normal file
119
lib/model/product_model.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/vo/product.dart';
|
||||
import 'package:fcs/vo/setting.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class ProductModel extends BaseModel {
|
||||
final log = Logger('ProductModel');
|
||||
|
||||
List<Product> products = [];
|
||||
List<Product> tempProducts = [];
|
||||
|
||||
List<ProductPrice> get getPrices {
|
||||
List<ProductPrice> productPrices = [];
|
||||
this.products.forEach((p) => productPrices.addAll(p.getPrices));
|
||||
productPrices.sort((p1, p2) => p1.compareTo(p2));
|
||||
return productPrices;
|
||||
}
|
||||
|
||||
String getProductName(String productID) {
|
||||
return products.firstWhere((p) => p.id == productID).name;
|
||||
}
|
||||
|
||||
Product getProduct(String productID) {
|
||||
return products.firstWhere((p) => p.id == productID);
|
||||
}
|
||||
|
||||
List<Product> get productsToEdit {
|
||||
// clone products
|
||||
tempProducts = products.map((p) => Product.clone(p)).toList();
|
||||
tempProducts.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
// set old price with price
|
||||
tempProducts.forEach((p) {
|
||||
p.oldPirce = p.price;
|
||||
p.action = "update";
|
||||
});
|
||||
|
||||
return tempProducts;
|
||||
}
|
||||
|
||||
void saveProduct(Product product, String name, price, displayOrder, int color,
|
||||
bool isDisable) {
|
||||
if (product == null) {
|
||||
tempProducts.add(Product(
|
||||
action: "create",
|
||||
name: name,
|
||||
price: int.parse(price),
|
||||
color: color,
|
||||
displayOrder: int.parse(displayOrder),
|
||||
isDisable: isDisable));
|
||||
} else {
|
||||
Product _product = product;
|
||||
_product.name = name;
|
||||
_product.price = int.parse(price);
|
||||
_product.color = color;
|
||||
_product.displayOrder = int.parse(displayOrder);
|
||||
_product.isDisable = isDisable;
|
||||
if (_product.id == null) {
|
||||
_product.action = "create";
|
||||
} else {
|
||||
_product.action = "update";
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void deleteProduct(Product product) {
|
||||
if (product == null) {
|
||||
return;
|
||||
}
|
||||
if (product.id == null) {
|
||||
tempProducts.remove(product);
|
||||
} else {
|
||||
Product _product = tempProducts.firstWhere((p) => p.id == product.id);
|
||||
_product.action = "delete";
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void initSetting(Setting setting) {
|
||||
super.initSetting(setting);
|
||||
|
||||
try {
|
||||
getQuerySnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$product_collection")
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
products.clear();
|
||||
products = snapshot.documents.map((documentSnapshot) {
|
||||
var data = Product.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
return data;
|
||||
}).toList();
|
||||
products.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
tempProducts = [];
|
||||
}
|
||||
|
||||
updateProducts(List<Product> products) async {
|
||||
var items = [];
|
||||
products.forEach((p) {
|
||||
if (p.action != null) items.add(p.toMap());
|
||||
});
|
||||
await request("/products", "PUT", payload: items, token: await getToken());
|
||||
}
|
||||
}
|
||||
86
lib/model/reg_model.dart
Normal file
86
lib/model/reg_model.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
import 'package:fcs/vo/buyer.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class Attachments {
|
||||
File nricFront, nricBack;
|
||||
}
|
||||
|
||||
class RegModel extends BaseModel {
|
||||
Buyer reg = Buyer();
|
||||
bool isLoaded = false;
|
||||
|
||||
StreamSubscription regListener;
|
||||
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
if (user.isRegisteredBuyer()) {
|
||||
_loadReg();
|
||||
} else {
|
||||
reg = Buyer();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
if (regListener != null) await regListener.cancel();
|
||||
reg = Buyer();
|
||||
}
|
||||
|
||||
Future<void> _loadReg() async {
|
||||
if (regListener != null) {
|
||||
regListener.cancel();
|
||||
}
|
||||
regListener = getDocSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection",
|
||||
"${user.docID}")
|
||||
.listen((snap) async {
|
||||
if (snap.exists) {
|
||||
reg = Buyer.fromMap(snap.data, snap.documentID);
|
||||
QuerySnapshot q = await getSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$buyer_collection/${user.docID}/$product_collection");
|
||||
reg.buyerProducts.clear();
|
||||
q.documents.forEach((d) {
|
||||
reg.buyerProducts.add(BuyerProduct.fromMap(d.data, d.documentID));
|
||||
});
|
||||
} else {
|
||||
reg = Buyer();
|
||||
}
|
||||
isLoaded = true;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> register(Buyer buyer, Attachments attachments) async {
|
||||
String path = Path.join(reg_files_path, user.docID);
|
||||
String urlFront = await uploadStorage(path, attachments.nricFront);
|
||||
buyer.nricFrontUrl = urlFront;
|
||||
String urlBack = await uploadStorage(path, attachments.nricBack);
|
||||
buyer.nricBackUrl = urlBack;
|
||||
|
||||
await request("/reg", "POST",
|
||||
payload: buyer.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> update(Buyer buyer, Attachments attachments) async {
|
||||
String path = Path.join(reg_files_path, user.docID);
|
||||
if (attachments.nricFront != null) {
|
||||
String urlFront = await uploadStorage(path, attachments.nricFront);
|
||||
buyer.nricFrontUrl = urlFront;
|
||||
}
|
||||
if (attachments.nricBack != null) {
|
||||
String urlBack = await uploadStorage(path, attachments.nricBack);
|
||||
buyer.nricBackUrl = urlBack;
|
||||
}
|
||||
|
||||
await request("/buyer/update", "PUT",
|
||||
payload: buyer.toMap(), token: await getToken());
|
||||
}
|
||||
}
|
||||
700
lib/model/report_model.dart
Normal file
700
lib/model/report_model.dart
Normal file
@@ -0,0 +1,700 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fcs/vo/report.dart';
|
||||
import 'package:fcs/vo/report_user.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:fcs/model/api_helper.dart' as api;
|
||||
import 'package:fcs/vo/report.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
import 'shared_pref.dart';
|
||||
|
||||
class ReportModel extends BaseModel {
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
StreamSubscription<QuerySnapshot> listenerUser;
|
||||
|
||||
List<Report> reports = [];
|
||||
List<Report> userReports = [];
|
||||
List filterValue = [];
|
||||
|
||||
List<Report> get reportList {
|
||||
List<Report> _reports = [];
|
||||
if (user.isOwnerAndAbove() || user.hasAdmin()) {
|
||||
_reports = reports;
|
||||
} else {
|
||||
_reports.addAll(reports);
|
||||
userReports.forEach((r) {
|
||||
if (!_reports.contains(r)) {
|
||||
_reports.add(r);
|
||||
}
|
||||
});
|
||||
}
|
||||
_reports.sort((a, b) => a.display.compareTo(b.display));
|
||||
return _reports;
|
||||
}
|
||||
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
reports = [];
|
||||
_loadReports();
|
||||
}
|
||||
|
||||
Future<void> _loadReports() async {
|
||||
if (listener != null) listener.cancel();
|
||||
if (listenerUser != null) listenerUser.cancel();
|
||||
|
||||
if (user.isOwnerAndAbove() || user.hasAdmin()) {
|
||||
listener = Firestore.instance
|
||||
.collection("/$reports_collection")
|
||||
.snapshots()
|
||||
.listen((snaps) async {
|
||||
reports.clear();
|
||||
reports = snaps.documents.map((documentSnapshot) {
|
||||
var report = Report.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
return report;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
});
|
||||
} else {
|
||||
listener = Firestore.instance
|
||||
.collection("/$reports_collection")
|
||||
.where("for_all_users", isEqualTo: true)
|
||||
.snapshots()
|
||||
.listen((snaps) async {
|
||||
reports.clear();
|
||||
reports = snaps.documents.map((documentSnapshot) {
|
||||
var report = Report.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
return report;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
listenerUser = Firestore.instance
|
||||
.collection("/$report_user_collection")
|
||||
.where("user_id", isEqualTo: user.docID)
|
||||
.snapshots()
|
||||
.listen((snaps) async {
|
||||
userReports.clear();
|
||||
userReports = snaps.documents.map((documentSnapshot) {
|
||||
var user = ReportUser.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
var report = Report(id: user.reportID, display: user.reportName);
|
||||
return report;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void logout() {
|
||||
reports = [];
|
||||
if (listener != null) listener.cancel();
|
||||
if (listenerUser != null) listenerUser.cancel();
|
||||
}
|
||||
|
||||
Future<Report> getReport(String repID) async {
|
||||
String path = "/$reports_collection";
|
||||
|
||||
var snap = await Firestore.instance.collection(path).document(repID).get();
|
||||
Report report = Report.fromMap(snap.data, snap.documentID);
|
||||
return report;
|
||||
}
|
||||
|
||||
Future<List> getReportData(Report report, int limit, int offset) async {
|
||||
List reportDataList = [];
|
||||
var aggFun = [];
|
||||
var fields = [];
|
||||
var groupbys = [];
|
||||
// print('report => $report');
|
||||
try {
|
||||
report.fields.asMap().forEach((key, value) {
|
||||
if (value.aggFun == '') {
|
||||
aggFun.add('');
|
||||
} else {
|
||||
aggFun.add(value.aggFun);
|
||||
}
|
||||
});
|
||||
report.fields.asMap().forEach((key, value) {
|
||||
fields.add(value.name);
|
||||
});
|
||||
|
||||
String strFields;
|
||||
fields.forEach((element) {
|
||||
if (strFields == null) {
|
||||
strFields = element;
|
||||
} else {
|
||||
strFields = strFields + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
String strAgg;
|
||||
aggFun.forEach((element) {
|
||||
if (strAgg == null) {
|
||||
strAgg = element;
|
||||
} else {
|
||||
strAgg = strAgg + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
String strGroup;
|
||||
groupbys.forEach((element) {
|
||||
if (strGroup == null) {
|
||||
strGroup = element;
|
||||
} else {
|
||||
strGroup = strGroup + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
var data = {
|
||||
"fields": strFields == null ? '' : strFields,
|
||||
"aggfuns": strAgg == null ? '' : strAgg,
|
||||
// "groupbys": strGroup == null ? '' : strGroup,
|
||||
"limit": limit,
|
||||
"offset": offset
|
||||
};
|
||||
print("payload:$data");
|
||||
|
||||
var rdata = {
|
||||
"fields": 'quantity,product_id,product_name',
|
||||
"aggfuns": ",,",
|
||||
"groupbys": 'product_id',
|
||||
"limit": limit,
|
||||
"offset": offset
|
||||
};
|
||||
var result = await request("/api/data/${report.object}", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
|
||||
if (result == null) return [];
|
||||
result.forEach((rdata) {
|
||||
reportDataList.add(rdata);
|
||||
});
|
||||
// print('reportDataList => $reportDataList');
|
||||
notifyListeners();
|
||||
return reportDataList;
|
||||
} catch (e) {
|
||||
log.warning("Error get Summary>>>>${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> downloadReportData(Report report) async {
|
||||
var aggFun = [];
|
||||
var fields = [];
|
||||
report.fields.asMap().forEach((key, value) {
|
||||
if (value.aggFun == '') {
|
||||
aggFun.add('');
|
||||
} else {
|
||||
aggFun.add(value.aggFun);
|
||||
}
|
||||
});
|
||||
report.fields.asMap().forEach((key, value) {
|
||||
fields.add(value.name);
|
||||
});
|
||||
|
||||
String strFields;
|
||||
fields.forEach((element) {
|
||||
if (strFields == null) {
|
||||
strFields = element;
|
||||
} else {
|
||||
strFields = strFields + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
String strAgg;
|
||||
aggFun.forEach((element) {
|
||||
if (strAgg == null) {
|
||||
strAgg = element;
|
||||
} else {
|
||||
strAgg = strAgg + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
// final directory = await getApplicationDocumentsDirectory();
|
||||
final directory = await getExternalStorageDirectory();
|
||||
String path = ('${directory.path}/${report.id}.pdf');
|
||||
log.info("download file path:$path");
|
||||
|
||||
var data = {
|
||||
"fields": strFields == null ? '' : strFields,
|
||||
"aggfuns": strAgg == null ? '' : strAgg,
|
||||
"greoupbys": '',
|
||||
"limit": 0,
|
||||
"offset": 0
|
||||
};
|
||||
|
||||
await api.requestDownloadPDFAPI("/api/report-pdf/${report.object}", "GET",
|
||||
filePath: path,
|
||||
url: Config.instance.reportURL,
|
||||
token: await getToken(),
|
||||
payload: jsonEncode(data));
|
||||
|
||||
final message = await OpenFile.open(path);
|
||||
log.info("Open file result:$message");
|
||||
}
|
||||
|
||||
Future<String> getJson(Report report) async {
|
||||
var aggFun = [];
|
||||
var fields = [];
|
||||
report.fields.asMap().forEach((key, value) {
|
||||
if (value.aggFun == '') {
|
||||
aggFun.add('');
|
||||
} else {
|
||||
aggFun.add(value.aggFun);
|
||||
}
|
||||
});
|
||||
report.fields.asMap().forEach((key, value) {
|
||||
fields.add(value.name);
|
||||
});
|
||||
|
||||
String strFields;
|
||||
fields.forEach((element) {
|
||||
if (strFields == null) {
|
||||
strFields = element;
|
||||
} else {
|
||||
strFields = strFields + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
String strAgg;
|
||||
aggFun.forEach((element) {
|
||||
if (strAgg == null) {
|
||||
strAgg = element;
|
||||
} else {
|
||||
strAgg = strAgg + ',' + element;
|
||||
}
|
||||
});
|
||||
|
||||
var data = {
|
||||
"fields": strFields == null ? '' : strFields,
|
||||
"aggfuns": strAgg == null ? '' : strAgg,
|
||||
"greoupbys": '',
|
||||
"limit": 0,
|
||||
"offset": 0
|
||||
};
|
||||
return jsonEncode(data);
|
||||
}
|
||||
|
||||
Future<String> getEscapeJson(Report report) async {
|
||||
var bytes = utf8.encode(await getJson(report));
|
||||
var base64Str = base64.encode(bytes);
|
||||
return HtmlEscape().convert(base64Str);
|
||||
}
|
||||
|
||||
Future<List> getReportDataWithFilters(Report report, List filters) async {
|
||||
List reportDataList = [];
|
||||
var aggFun = [];
|
||||
var fields = [];
|
||||
var groupbys = [];
|
||||
|
||||
try {
|
||||
var data = report.convertArrayToString(report, filters);
|
||||
print('data=> $data');
|
||||
|
||||
var result = await request("/api/data/${report.object}", "POST",
|
||||
token: await getToken(),
|
||||
url: Config.instance.reportURL,
|
||||
payload: jsonEncode(data));
|
||||
|
||||
if (result == null) return [];
|
||||
result.forEach((rdata) {
|
||||
reportDataList.add(rdata);
|
||||
});
|
||||
|
||||
notifyListeners();
|
||||
return reportDataList;
|
||||
} catch (e) {
|
||||
log.warning("Error get Summary>>>>${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addreport() async {
|
||||
var data = [
|
||||
{
|
||||
"display": "Buyer Rpt",
|
||||
"object": "buyer_rpt",
|
||||
"display_filters": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"compare": "==",
|
||||
"data_type": "string",
|
||||
},
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "reg_date",
|
||||
"display_name": "Registered Date",
|
||||
"to_fixed": 0,
|
||||
"type": "intdate",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "biz_name",
|
||||
"display_name": "Business Name",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "biz_address",
|
||||
"display_name": "Business Address",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
],
|
||||
"display_fields": ['biz_name', 'biz_name', 'reg_date', 'user_name'],
|
||||
"sorts": [],
|
||||
"groupbys": []
|
||||
},
|
||||
{
|
||||
"display": "Delivery Rpt",
|
||||
"object": "delivery_rpt",
|
||||
"display_filters": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"compare": "==",
|
||||
"data_type": "string",
|
||||
},
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "delivery_ended_date",
|
||||
"display_name": "Delivery Ended Date",
|
||||
"to_fixed": 0,
|
||||
"type": "intdate",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_92",
|
||||
"display_name": "Qty 92",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_95",
|
||||
"display_name": "Qty 95",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_d",
|
||||
"display_name": "Qty D",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_p",
|
||||
"display_name": "Qty P",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
],
|
||||
"display_fields": [
|
||||
'qty_p',
|
||||
'qty_d',
|
||||
'qty_95',
|
||||
'qty_92',
|
||||
'delivery_ended_date',
|
||||
'user_name',
|
||||
],
|
||||
"sorts": [],
|
||||
"groupbys": []
|
||||
},
|
||||
{
|
||||
"display": "DOs Rpt",
|
||||
"object": "dos_rpt",
|
||||
"display_filters": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"compare": "==",
|
||||
"data_type": "string",
|
||||
},
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "doc_date",
|
||||
"display_name": "DO Date",
|
||||
"to_fixed": 0,
|
||||
"type": "intdate",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "doc_number",
|
||||
"display_name": "PO/DO Number",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_92",
|
||||
"display_name": "Qty 92",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_95",
|
||||
"display_name": "Qty 95",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_d",
|
||||
"display_name": "Qty D",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_p",
|
||||
"display_name": "Qty P",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
],
|
||||
"display_fields": [
|
||||
'qty_p',
|
||||
'qty_d',
|
||||
'qty_95',
|
||||
'qty_92',
|
||||
'doc_date',
|
||||
'user_name',
|
||||
'doc_number'
|
||||
],
|
||||
"sorts": [],
|
||||
"groupbys": []
|
||||
},
|
||||
{
|
||||
"display": "POs Rpt",
|
||||
"object": "pos_rpt",
|
||||
"display_filters": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"compare": "==",
|
||||
"data_type": "string",
|
||||
},
|
||||
{
|
||||
"name": "po_number",
|
||||
"display_name": "PO Number",
|
||||
"compare": "==",
|
||||
"data_type": "string",
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "po_date",
|
||||
"display_name": "PO Date",
|
||||
"to_fixed": 0,
|
||||
"type": "intdate",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "po_number",
|
||||
"display_name": "PO Number",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_92",
|
||||
"display_name": "Qty 92",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_95",
|
||||
"display_name": "Qty 95",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_d",
|
||||
"display_name": "Qty D",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "qty_p",
|
||||
"display_name": "Qty P",
|
||||
"to_fixed": 0,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "price_92",
|
||||
"display_name": "Price 92",
|
||||
"to_fixed": 0,
|
||||
"type": "integer",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "price_95",
|
||||
"display_name": "Price 95",
|
||||
"to_fixed": 0,
|
||||
"type": "integer",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "price_d",
|
||||
"display_name": "Price D",
|
||||
"to_fixed": 0,
|
||||
"type": "integer",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "price_p",
|
||||
"display_name": "Price P",
|
||||
"to_fixed": 0,
|
||||
"type": "integer",
|
||||
"agg_fun": "",
|
||||
}
|
||||
],
|
||||
"display_fields": [
|
||||
'qty_p',
|
||||
'qty_d',
|
||||
'qty_95',
|
||||
'qty_92',
|
||||
'price_p',
|
||||
'price_d',
|
||||
'price_95',
|
||||
'price_92',
|
||||
'po_date',
|
||||
'user_name',
|
||||
'po_number'
|
||||
],
|
||||
"sorts": [],
|
||||
"groupbys": []
|
||||
},
|
||||
{
|
||||
"display": "Storage Charge Rpt",
|
||||
"object": "storage_charge_rpt",
|
||||
"display_filters": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"compare": "==",
|
||||
"data_type": "string",
|
||||
},
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "user_name",
|
||||
"display_name": "User Name",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "total_qty",
|
||||
"display_name": "Quantity",
|
||||
"to_fixed": 3,
|
||||
"type": "float",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "rate",
|
||||
"display_name": "Rate",
|
||||
"to_fixed": 0,
|
||||
"type": "string",
|
||||
"agg_fun": "",
|
||||
},
|
||||
{
|
||||
"name": "storage_charge",
|
||||
"display_name": "Amount",
|
||||
"to_fixed": 3,
|
||||
"type": "integer",
|
||||
"agg_fun": "",
|
||||
},
|
||||
],
|
||||
"display_fields": [
|
||||
'qty_p',
|
||||
'qty_d',
|
||||
'qty_95',
|
||||
'qty_92',
|
||||
'price_p',
|
||||
'price_d',
|
||||
'price_95',
|
||||
'price_92',
|
||||
'po_date',
|
||||
'user_name',
|
||||
'po_number'
|
||||
],
|
||||
"sorts": [],
|
||||
"groupbys": []
|
||||
}
|
||||
];
|
||||
|
||||
data.asMap().forEach((key, value) {
|
||||
Firestore.instance
|
||||
.collection("/$reports_collection")
|
||||
.document()
|
||||
.setData(value);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> saveSelectedFieldsAndPosition(
|
||||
String id, List<Field> positonFields, List selectedFields) async {
|
||||
// positonFields.toJson
|
||||
ReportFieldPositionSelection report = ReportFieldPositionSelection(
|
||||
fieldPosition: positonFields, fieldSelection: selectedFields);
|
||||
await SharedPref.saveReport(report);
|
||||
}
|
||||
|
||||
Future<ReportFieldPositionSelection> loadSelectedFieldsAndPosition(
|
||||
String id) async {
|
||||
var data = await SharedPref.getReport(id);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
83
lib/model/report_user_model.dart
Normal file
83
lib/model/report_user_model.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fcs/model/constants.dart';
|
||||
import 'package:fcs/vo/report.dart';
|
||||
import 'package:fcs/vo/report_user.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import 'base_model.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class ReportUserModel extends BaseModel {
|
||||
List<ReportUser> reportUsers = [];
|
||||
void initUser(user) {
|
||||
super.initUser(user);
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {}
|
||||
|
||||
Future<List<ReportUser>> getUsersForReport(String reportID) async {
|
||||
List<ReportUser> users = [];
|
||||
String path = "/$report_user_collection";
|
||||
|
||||
QuerySnapshot snapshots = await Firestore.instance
|
||||
.collection(path)
|
||||
.where('report_id', isEqualTo: reportID)
|
||||
.getDocuments();
|
||||
snapshots.documents.forEach((d) {
|
||||
users.add(ReportUser.fromMap(d.data, d.documentID));
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
||||
Future<List<User>> findUser(String searchUser) async {
|
||||
if (searchUser == null || searchUser == '') return List();
|
||||
|
||||
var bytes = utf8.encode(searchUser);
|
||||
var base64Str = base64.encode(bytes);
|
||||
HtmlEscape htmlEscape = const HtmlEscape();
|
||||
String escapeUser = htmlEscape.convert(base64Str);
|
||||
|
||||
int limit = 20;
|
||||
List<User> _users = [];
|
||||
|
||||
try {
|
||||
var data = await request(
|
||||
"/api/fts/$user_collection/$escapeUser/$limit", "GET",
|
||||
token: await getToken(), url: Config.instance.reportURL);
|
||||
if (data == null) return List();
|
||||
|
||||
data.forEach((user) {
|
||||
var _user = User.fromUserJson(user);
|
||||
_users.add(_user);
|
||||
});
|
||||
} catch (e) {
|
||||
// permission error
|
||||
log.warning("user error:" + e.toString());
|
||||
return null;
|
||||
}
|
||||
return _users;
|
||||
}
|
||||
|
||||
Future<void> updateReportForAllUsers(Report report) async {
|
||||
await request("/report", "PUT",
|
||||
payload: {'id': report.id, 'for_all_users': report.forAllUser},
|
||||
token: await getToken());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> assignUser(ReportUser reportUser) async {
|
||||
await request("/report_user", "POST",
|
||||
payload: reportUser.toMap(), token: await getToken());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> deleteReportUser(ReportUser reportUser) async {
|
||||
await request("/report_user", "DELETE",
|
||||
payload: reportUser.toMap(), token: await getToken());
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
76
lib/model/shared_pref.dart
Normal file
76
lib/model/shared_pref.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:fcs/vo/report.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fcs/vo/user.dart';
|
||||
|
||||
class SharedPref {
|
||||
static final SharedPref instance = SharedPref._();
|
||||
SharedPref._();
|
||||
|
||||
static Future<String> getLang() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('language');
|
||||
}
|
||||
|
||||
static Future<void> saveLang(String lang) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('language', lang);
|
||||
}
|
||||
|
||||
static Future<void> saveUser(User user) async {
|
||||
await _save("user", user.toJson());
|
||||
}
|
||||
|
||||
static Future<void> saveReport(ReportFieldPositionSelection report) async {
|
||||
await _save('report-${report.id}', report.toJson());
|
||||
}
|
||||
|
||||
static Future<ReportFieldPositionSelection> getReport(String id) async {
|
||||
try {
|
||||
return ReportFieldPositionSelection.fromJson(await _read("report-$id"));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<User> getUser() async {
|
||||
try {
|
||||
return User.fromJson(await _read("user"));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<User> removeUser() async {
|
||||
return await _remove("user");
|
||||
}
|
||||
|
||||
static Future<void> saveSkippedRecoverEmail(bool skipped) async {
|
||||
await _save("skipped_recovery_email", skipped);
|
||||
}
|
||||
|
||||
static Future<bool> getSkippedRecoverEmail() async {
|
||||
try {
|
||||
bool _skipped = await _read("skipped_recovery_email");
|
||||
return _skipped;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static _read(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return json.decode(prefs.getString(key));
|
||||
}
|
||||
|
||||
static _save(String key, value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(key, json.encode(value));
|
||||
}
|
||||
|
||||
static _remove(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove(key);
|
||||
}
|
||||
}
|
||||
155
lib/model/storage_model.dart
Normal file
155
lib/model/storage_model.dart
Normal file
@@ -0,0 +1,155 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/vo/inventory_line.dart';
|
||||
import 'package:fcs/vo/inventory_taking.dart';
|
||||
import 'package:fcs/vo/product.dart';
|
||||
import 'package:fcs/vo/storage.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class StorageModel extends BaseModel {
|
||||
final log = Logger('StorageModel');
|
||||
|
||||
List<Storage> storages = [];
|
||||
List<InventoryTaking> inventoryTakings = [];
|
||||
int selectedIndex = 0;
|
||||
DateTime selectedDate = DateTime.now();
|
||||
|
||||
List<Storage> getStorage(String productID) {
|
||||
return storages
|
||||
.where((s) => s.products.any((p) => p.id == productID))
|
||||
.toList();
|
||||
}
|
||||
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
if (!user.isOwnerAndAbove() && !user.hasInventory()) {
|
||||
return;
|
||||
}
|
||||
_loadStorages();
|
||||
_loadInventoryTakings();
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
storages = [];
|
||||
inventoryTakings = [];
|
||||
}
|
||||
|
||||
String getStorageName(String storageID) {
|
||||
return storages.firstWhere((s) => s.id == storageID).name;
|
||||
}
|
||||
|
||||
void _loadStorages() async {
|
||||
try {
|
||||
getQuerySnapshotF(
|
||||
"/$biz_collection/${setting.okEnergyId}/$storage_collection",
|
||||
user.accountID)
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
storages.clear();
|
||||
storages = snapshot.documents.map((documentSnapshot) {
|
||||
var storage = Storage.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
loadProducts(storage);
|
||||
return storage;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _loadInventoryTakings() async {
|
||||
try {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$inventory_takings";
|
||||
var startDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 0, 0, 0);
|
||||
var endDate = new DateTime(
|
||||
selectedDate.year, selectedDate.month, selectedDate.day, 23, 59, 59);
|
||||
|
||||
inventoryTakings.clear();
|
||||
getFilterDateSnapshotF(path, user.accountID, 'date_time', startDate,
|
||||
endDate, 'date_time')
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
inventoryTakings = snapshot.documents.map((documentSnapshot) {
|
||||
var data = InventoryTaking.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
|
||||
return data;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
loadInventoryLines(InventoryTaking inventoryTaking) async {
|
||||
if (inventoryTaking.linesLoaded) return;
|
||||
var snaps = await getSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$inventory_takings/${inventoryTaking.id}/$inventory_lines");
|
||||
inventoryTaking.inventoryLines =
|
||||
snaps.documents.map((s) => InventoryLine.fromMap(s.data)).toList();
|
||||
inventoryTaking.linesLoaded = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
loadProducts(Storage storage) async {
|
||||
if (storage.productsLoaded) return;
|
||||
var snaps = await getSnapshot(
|
||||
"/$biz_collection/${setting.okEnergyId}/$storage_collection/${storage.id}/$product_collection");
|
||||
storage.products = snaps.documents
|
||||
.map((s) => Product.fromMap(s.data, s.documentID))
|
||||
.toList();
|
||||
storage.productsLoaded = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> createStorage(Storage storage) async {
|
||||
await request("/storage", "POST",
|
||||
payload: storage.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updateStorage(Storage storage) async {
|
||||
await request("/storage", "PUT",
|
||||
payload: storage.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> deleteStorage(String storageID) async {
|
||||
await request("/storage/" + storageID, "DELETE", token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> createInventoryTaking(InventoryTaking inventoryTaking) async {
|
||||
await request("/inventory", "POST",
|
||||
payload: inventoryTaking.toMap(), token: await getToken());
|
||||
}
|
||||
|
||||
void filterDate(DateTime dateTime, int _selectedIndex) async {
|
||||
this.selectedIndex = _selectedIndex;
|
||||
this.selectedDate = dateTime;
|
||||
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$inventory_takings";
|
||||
var endDate =
|
||||
new DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59);
|
||||
inventoryTakings.clear();
|
||||
getFilterDateSnapshotF(
|
||||
path, user.accountID, 'date_time', dateTime, endDate, 'date_time')
|
||||
.listen((snapshot) {
|
||||
inventoryTakings = snapshot.documents.map((documentSnapshot) {
|
||||
var data = InventoryTaking.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
|
||||
return data;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
177
lib/model/test_model.dart
Normal file
177
lib/model/test_model.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:faker/faker.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'base_model.dart';
|
||||
|
||||
class TestModel extends BaseModel {
|
||||
final log = Logger('TestModel');
|
||||
|
||||
List<Test> tests = [];
|
||||
DocumentSnapshot prev;
|
||||
static const int row_count = 10;
|
||||
bool ended = false;
|
||||
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
final Query queryBase = Firestore.instance.collection("/tests");
|
||||
final Query query = Firestore.instance
|
||||
.collection("/tests")
|
||||
// .orderBy("deleted")
|
||||
.orderBy("age", descending: false);
|
||||
|
||||
void initData() async {
|
||||
_clearState();
|
||||
_initListener();
|
||||
load();
|
||||
}
|
||||
|
||||
void _clearState() {
|
||||
prev = null;
|
||||
tests = [];
|
||||
ended = false;
|
||||
if (listener != null) listener.cancel();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
void _initListener() {
|
||||
Query _query = queryBase.orderBy("update_time", descending: true).limit(1);
|
||||
_query.getDocuments(source: Source.server).then((QuerySnapshot snapshot) {
|
||||
int count = snapshot.documents.length;
|
||||
if (count == 1) {
|
||||
var test = Test.fromMap(
|
||||
snapshot.documents[0].data, snapshot.documents[0].documentID);
|
||||
|
||||
Query _queryListener = queryBase
|
||||
.where("update_time", isGreaterThan: test.updateTime)
|
||||
.orderBy("update_time", descending: true);
|
||||
|
||||
listener =
|
||||
_queryListener.snapshots(includeMetadataChanges: true).listen((qs) {
|
||||
qs.documentChanges.forEach((c) {
|
||||
switch (c.type) {
|
||||
case DocumentChangeType.added:
|
||||
var test = Test.fromMap(c.document.data, c.document.documentID);
|
||||
if (tests.contains(test)) {
|
||||
tests[tests.indexOf(test)].name = test.name;
|
||||
notifyListeners();
|
||||
}
|
||||
if (!tests.contains(test)) {
|
||||
tests.add(test);
|
||||
notifyListeners();
|
||||
}
|
||||
break;
|
||||
case DocumentChangeType.modified:
|
||||
var test = Test.fromMap(c.document.data, c.document.documentID);
|
||||
if (tests.contains(test)) {
|
||||
bool deleted = c.document.data["deleted"];
|
||||
if (deleted != null && deleted) {
|
||||
tests.remove(test);
|
||||
} else {
|
||||
tests[tests.indexOf(test)].name = test.name;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
Query _query = prev != null ? query.startAfterDocument(prev) : query;
|
||||
try {
|
||||
_query
|
||||
// .where("deleted", isNull: null)
|
||||
.limit(row_count)
|
||||
.getDocuments(source: Source.server)
|
||||
.then((QuerySnapshot snapshot) {
|
||||
int count = snapshot.documents.length;
|
||||
ended = count < row_count;
|
||||
prev = count > 0 ? snapshot.documents[count - 1] : prev;
|
||||
snapshot.documents.forEach((e) {
|
||||
var test = Test.fromMap(e.data, e.documentID);
|
||||
if (!tests.contains(test)) tests.add(test);
|
||||
});
|
||||
notifyListeners();
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
void populate() {
|
||||
for (var i = 0; i < 30; i++) {
|
||||
Firestore.instance
|
||||
.collection('tests')
|
||||
.document(faker.person.name())
|
||||
.setData({
|
||||
'name': faker.person.name(),
|
||||
'age': random.decimal(),
|
||||
'update_time': DateTime.now().microsecondsSinceEpoch
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void add() {
|
||||
Firestore.instance
|
||||
.collection('tests')
|
||||
.document(faker.person.name())
|
||||
.setData({
|
||||
'name': faker.person.name(),
|
||||
'age': random.decimal(),
|
||||
'update_time': DateTime.now().microsecondsSinceEpoch
|
||||
});
|
||||
}
|
||||
|
||||
void update() {
|
||||
Firestore.instance.collection('tests').document(tests[0].id).setData({
|
||||
'name': faker.person.name(),
|
||||
'update_time': DateTime.now().microsecondsSinceEpoch
|
||||
}, merge: true);
|
||||
}
|
||||
|
||||
void remove() {
|
||||
Firestore.instance.collection('tests').document(tests[0].id).setData(
|
||||
{'deleted': 1, 'update_time': DateTime.now().microsecondsSinceEpoch},
|
||||
merge: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void logout() {
|
||||
_clearState();
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
String id;
|
||||
String name;
|
||||
double age;
|
||||
int updateTime;
|
||||
Test(this.id, {this.name, this.age, this.updateTime});
|
||||
|
||||
factory Test.fromMap(Map<String, dynamic> map, String id) {
|
||||
return Test(id,
|
||||
name: map['name'], age: map['age'], updateTime: map['update_time']);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return other.id == this.id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
int result = 17;
|
||||
result = 37 * result + id.hashCode;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
368
lib/model/user_model.dart
Normal file
368
lib/model/user_model.dart
Normal file
@@ -0,0 +1,368 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/util.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/vo/role.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import '../vo/user.dart';
|
||||
import 'base_model.dart';
|
||||
import 'constants.dart';
|
||||
import 'firebase_helper.dart';
|
||||
|
||||
class UserModel extends BaseModel {
|
||||
final log = Logger('UserModel');
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
PopupMenu popupMenu = new PopupMenu();
|
||||
|
||||
List<User> users = [];
|
||||
List<Privilege> privileges = [];
|
||||
List<UserLevel> userLevels = [];
|
||||
User user = new User();
|
||||
|
||||
List<Privilege> get getPrivileges {
|
||||
return privileges
|
||||
.where((p) => !p.sysAdminOnly || user.isSysAdmin())
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<dynamic> cont = [];
|
||||
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
this.user = user;
|
||||
if (user.isBuyer()) return;
|
||||
_loadUsers(user);
|
||||
_loadPrivileges();
|
||||
_loadUserLevels(user);
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
users = [];
|
||||
userLevels = [];
|
||||
}
|
||||
|
||||
List<Privilege> getUserPrivileges() {
|
||||
List<Privilege> result = new List();
|
||||
if (user.privilegeIds.isNotEmpty) {
|
||||
user.privilegeIds.forEach((pID) {
|
||||
privileges.forEach((p) {
|
||||
if (p.id == pID) {
|
||||
var _priv = Privilege(id: p.id, name: p.name, desc: p.desc);
|
||||
result.add(_priv);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<User> getBlockListUsers() {
|
||||
return users.where((u) => u.isBlock == true).toList();
|
||||
}
|
||||
|
||||
List<User> getUserList() {
|
||||
return users.where((u) => u.docID != this.user.docID).toList();
|
||||
}
|
||||
|
||||
Future<void> _loadUsers(User user) async {
|
||||
try {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$user_collection";
|
||||
var snaps = await Firestore.instance
|
||||
.collection(path)
|
||||
.where('user_level', isLessThanOrEqualTo: user.userLevel)
|
||||
.limit(1)
|
||||
.getDocuments();
|
||||
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where('user_level', isLessThanOrEqualTo: user.userLevel)
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
users = snaps.documents.map((documentSnapshot) {
|
||||
var data =
|
||||
User.fromMap(documentSnapshot.data, documentSnapshot.documentID);
|
||||
if (data.docID == user.docID && data.isBlock) {
|
||||
this.mainModel.logout();
|
||||
notifyListeners();
|
||||
}
|
||||
return data;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<User> findUser(String phoneNumber) async {
|
||||
var _phoneNumber = updatePhoneNumber(phoneNumber);
|
||||
try {
|
||||
var data = await request("/user/find/$_phoneNumber", "GET",
|
||||
token: await getToken());
|
||||
return User.fromJson(data);
|
||||
} catch (e) {
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadPrivileges() async {
|
||||
// if (!user.isOwner() && !user.hasAccount()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
getQuerySnapshot("/$privilege_collection")
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
privileges.clear();
|
||||
privileges = snapshot.documents.map((documentSnapshot) {
|
||||
var privilege = Privilege.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID);
|
||||
return privilege;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
}).onError((e) {
|
||||
log.warning("Error! $e");
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadUserLevels(User user) async {
|
||||
try {
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection("/$user_level_collection")
|
||||
.where('level', isLessThan: user.userLevel)
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
userLevels = snaps.documents
|
||||
.map((documentSnapshot) => UserLevel.fromMap(
|
||||
documentSnapshot.data, documentSnapshot.documentID))
|
||||
.toList();
|
||||
notifyListeners();
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<User> getUser(String id) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$user_collection";
|
||||
print("id=> $id");
|
||||
var snap = await getDocSnap(path, id);
|
||||
print("snap=> $snap");
|
||||
return User.fromMap(snap.data, snap.documentID);
|
||||
}
|
||||
|
||||
Future<void> addUserToOk(String userID, List<String> privileges) async {
|
||||
await addUserToBiz(userID, setting.okEnergyId, privileges);
|
||||
}
|
||||
|
||||
Future<void> addUserToBiz(
|
||||
String userID, bizID, List<String> privileges) async {
|
||||
try {
|
||||
await request("/user/add-biz", "POST",
|
||||
payload: {
|
||||
"user_id": userID,
|
||||
"biz_id": bizID,
|
||||
"privileges": privileges
|
||||
},
|
||||
token: await getToken());
|
||||
} catch (e) {
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> forgetPassword(String id) async {
|
||||
var _id = updatePhoneNumber(id);
|
||||
await request("/forget", "POST", payload: {"id": _id});
|
||||
}
|
||||
|
||||
Future<void> resetPassword(
|
||||
String id, String newPassword, String confirmationCode) async {
|
||||
var _id = updatePhoneNumber(id);
|
||||
await request(
|
||||
"/reset",
|
||||
"POST",
|
||||
payload: {
|
||||
"id": _id,
|
||||
"password": newPassword,
|
||||
"confirmation_code": confirmationCode
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> changePassword(String id, String newPassword) async {
|
||||
await request(
|
||||
"/change/password",
|
||||
"POST",
|
||||
payload: {
|
||||
"id": id,
|
||||
"password": newPassword,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> changePhone(String id, String newPhone) async {
|
||||
var _newPhone = updatePhoneNumber(newPhone);
|
||||
await request(
|
||||
"/change/phone",
|
||||
"POST",
|
||||
payload: {
|
||||
"id": id,
|
||||
"phone_number": _newPhone,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addEmail(String id, String email) async {
|
||||
await request(
|
||||
"/email",
|
||||
"PUT",
|
||||
payload: {
|
||||
"id": id,
|
||||
"email": email,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> confirmEmail(
|
||||
String id, String email, String phone, String confirmCode) async {
|
||||
var _id = updatePhoneNumber(id);
|
||||
var _phone = updatePhoneNumber(phone);
|
||||
await request(
|
||||
"/econfirm",
|
||||
"POST",
|
||||
payload: {
|
||||
"id": _id,
|
||||
"email": email == null ? '' : email,
|
||||
"phone_number": _phone == null ? '' : _phone,
|
||||
"confirmation_code": confirmCode
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteStorage(String storageID) async {
|
||||
await request("/storage/" + storageID, "DELETE", token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> blockPhone(String phone) async {
|
||||
var _phone = updatePhoneNumber(phone);
|
||||
await request("/blist", "PUT",
|
||||
payload: {"phone_number": _phone}, token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> unblockPhone(String phone) async {
|
||||
var _phone = updatePhoneNumber(phone);
|
||||
await request("/wlist", "PUT",
|
||||
payload: {"phone_number": _phone}, token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> addLevel(
|
||||
String phone, String levelId, List<String> privs) async {
|
||||
var _phone = updatePhoneNumber(phone);
|
||||
await request("/lvl", "PUT",
|
||||
payload: {
|
||||
"phone_number": _phone,
|
||||
"user_level_id": levelId,
|
||||
"privileges": privs
|
||||
},
|
||||
token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> updatePin(String pin, String password) async {
|
||||
await request("/pin", "POST",
|
||||
payload: {
|
||||
"id": user.docID,
|
||||
"pin": pin,
|
||||
"password": password,
|
||||
},
|
||||
token: await getToken());
|
||||
}
|
||||
|
||||
Future<void> clearPin(String password) async {
|
||||
await request("/pin/clear", "POST",
|
||||
payload: {
|
||||
"id": user.docID,
|
||||
"password": password,
|
||||
},
|
||||
token: await getToken());
|
||||
}
|
||||
|
||||
Future<List<User>> searchUser(String searchUser) async {
|
||||
if (searchUser == null || searchUser == '') return List();
|
||||
|
||||
var bytes = utf8.encode(searchUser);
|
||||
var base64Str = base64.encode(bytes);
|
||||
HtmlEscape htmlEscape = const HtmlEscape();
|
||||
String escapeUser = htmlEscape.convert(base64Str);
|
||||
|
||||
int limit = 20;
|
||||
List<User> _users = [];
|
||||
|
||||
try {
|
||||
var data = await request(
|
||||
"/api/fts/$user_collection/$escapeUser/$limit", "GET",
|
||||
token: await getToken(), url: Config.instance.reportURL);
|
||||
if (data == null) return List();
|
||||
|
||||
data.forEach((user) {
|
||||
var _user = User.fromUserJson(user);
|
||||
_users.add(_user);
|
||||
});
|
||||
} catch (e) {
|
||||
// permission error
|
||||
log.warning("user error:" + e.toString());
|
||||
return null;
|
||||
}
|
||||
return _users;
|
||||
}
|
||||
|
||||
void filterSorting(int _selectedIndex) {
|
||||
users.clear();
|
||||
if (listener != null) {
|
||||
listener.cancel();
|
||||
}
|
||||
String _fieldName;
|
||||
bool descending = false;
|
||||
|
||||
if (_selectedIndex == 0) {
|
||||
_fieldName = 'user_name';
|
||||
descending = false;
|
||||
}
|
||||
if (_selectedIndex == 1) {
|
||||
_fieldName = 'user_name';
|
||||
descending = true;
|
||||
}
|
||||
if (_selectedIndex == 2) {
|
||||
_fieldName = 'phone_number';
|
||||
descending = false;
|
||||
}
|
||||
if (_selectedIndex == 3) {
|
||||
_fieldName = 'phone_number';
|
||||
descending = true;
|
||||
}
|
||||
|
||||
this.popupMenu.index = _selectedIndex;
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$user_collection";
|
||||
listener =
|
||||
getFilterSnapshot(path, descending, _fieldName).listen((snaps) async {
|
||||
users.clear();
|
||||
snaps.documents.forEach((d) {
|
||||
users.add(User.fromMap(d.data, d.documentID));
|
||||
notifyListeners();
|
||||
});
|
||||
users.where((user) => user.userLevel <= this.user.userLevel);
|
||||
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
81
lib/model_page/user_list_model.dart
Normal file
81
lib/model_page/user_list_model.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:fcs/model/base_model.dart';
|
||||
import 'package:fcs/model/constants.dart';
|
||||
import 'package:fcs/model/firebase_helper.dart';
|
||||
import 'package:fcs/util.dart';
|
||||
|
||||
import '../vo/user.dart';
|
||||
|
||||
class UserModel extends BaseModel {
|
||||
final log = Logger('UserListModel');
|
||||
StreamSubscription<QuerySnapshot> listener;
|
||||
List<User> users = [];
|
||||
|
||||
void initUser(user) async {
|
||||
super.initUser(user);
|
||||
_loadUsers(user);
|
||||
}
|
||||
|
||||
Future<void> _loadUsers(User user) async {
|
||||
try {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$user_collection";
|
||||
var snaps = await Firestore.instance
|
||||
.collection(path)
|
||||
.where('user_level', isLessThanOrEqualTo: user.userLevel)
|
||||
.limit(1)
|
||||
.getDocuments();
|
||||
|
||||
Stream<QuerySnapshot> snapshots = Firestore.instance
|
||||
.collection(path)
|
||||
.where('user_level', isLessThanOrEqualTo: user.userLevel)
|
||||
.snapshots();
|
||||
|
||||
snapshots.listen((snaps) async {
|
||||
users = snaps.documents.map((documentSnapshot) {
|
||||
var data =
|
||||
User.fromMap(documentSnapshot.data, documentSnapshot.documentID);
|
||||
if (data.docID == user.docID && data.isBlock) {
|
||||
this.mainModel.logout();
|
||||
notifyListeners();
|
||||
}
|
||||
return data;
|
||||
}).toList();
|
||||
notifyListeners();
|
||||
});
|
||||
} catch (e) {
|
||||
log.warning("Error!! $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
logout() async {
|
||||
users = [];
|
||||
if (listener != null) listener.cancel();
|
||||
}
|
||||
|
||||
List<User> getUserList() {
|
||||
return users.where((u) => u.docID != this.user.docID).toList();
|
||||
}
|
||||
|
||||
Future<User> findUser(String phoneNumber) async {
|
||||
var _phoneNumber = updatePhoneNumber(phoneNumber);
|
||||
try {
|
||||
var data = await request("/user/find/$_phoneNumber", "GET",
|
||||
token: await getToken());
|
||||
return User.fromJson(data);
|
||||
} catch (e) {
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<User> getUser(String id) async {
|
||||
String path = "/$biz_collection/${setting.okEnergyId}/$user_collection";
|
||||
print("id=> $id");
|
||||
var snap = await getDocSnap(path, id);
|
||||
print("snap=> $snap");
|
||||
return User.fromMap(snap.data, snap.documentID);
|
||||
}
|
||||
}
|
||||
363
lib/pages/add_pin_editor.dart
Normal file
363
lib/pages/add_pin_editor.dart
Normal file
@@ -0,0 +1,363 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart' as Theme;
|
||||
import 'util.dart';
|
||||
|
||||
class AddPINEditor extends StatefulWidget {
|
||||
final User user;
|
||||
AddPINEditor(
|
||||
this.user, {
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AddPINEditorState createState() => new _AddPINEditorState();
|
||||
}
|
||||
|
||||
class _AddPINEditorState extends State<AddPINEditor>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
final FocusNode myFocusNodePassword = FocusNode();
|
||||
final FocusNode myFocusNodeEmail = FocusNode();
|
||||
|
||||
bool _obscureTextLogin = true;
|
||||
bool _obscureTextSignup = true;
|
||||
bool _obscureTextSignupConfirm = true;
|
||||
|
||||
TextEditingController _passwordController = new TextEditingController();
|
||||
TextEditingController _pinController = new TextEditingController();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
bool isSwitched = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height >= 775.0
|
||||
? MediaQuery.of(context).size.height
|
||||
: 580.0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 35.0, bottom: 10),
|
||||
child: ListTile(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: LocalText(
|
||||
context,
|
||||
'change.pin.title',
|
||||
color: Colors.black87,
|
||||
fontSize: 17,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PageView(
|
||||
children: <Widget>[
|
||||
new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: _buildReset(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myFocusNodePassword.dispose();
|
||||
myFocusNodeEmail.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// SystemChrome.setPreferredOrientations([
|
||||
// DeviceOrientation.portraitUp,
|
||||
// DeviceOrientation.portraitDown,
|
||||
// ]);
|
||||
}
|
||||
|
||||
Widget _buildReset(BuildContext context) {
|
||||
final switchBtnBox = Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: LocalText(
|
||||
context,
|
||||
'pin.switch',
|
||||
fontSize: 15,
|
||||
)),
|
||||
Switch(
|
||||
value: isSwitched,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
isSwitched = value;
|
||||
if (!isSwitched) {
|
||||
_pinController.clear();
|
||||
}
|
||||
});
|
||||
},
|
||||
activeColor: Theme.secondaryColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final pinInputBox = Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
controller: _pinController,
|
||||
keyboardType: TextInputType.number,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Image.asset(
|
||||
'assets/pin.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
),
|
||||
labelText: AppTranslations.of(context).text("change.pin"),
|
||||
labelStyle: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontFamily: "WorkSansSemiBold", color: Colors.grey)
|
||||
: TextStyle(fontFamily: "MyanmarUnicode", color: Colors.grey),
|
||||
),
|
||||
validator: _validatePinCode,
|
||||
),
|
||||
);
|
||||
|
||||
final passwordInputBox = Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodePassword,
|
||||
controller: _passwordController,
|
||||
obscureText: _obscureTextSignup,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.lock,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context).text("login.password"),
|
||||
labelStyle: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontFamily: "WorkSansSemiBold", color: Colors.grey)
|
||||
: TextStyle(fontFamily: "MyanmarUnicode", color: Colors.grey),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: _toggleSignup,
|
||||
child: Icon(
|
||||
_obscureTextSignup
|
||||
? FontAwesomeIcons.eye
|
||||
: FontAwesomeIcons.eyeSlash,
|
||||
size: 15.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: _validatePassword,
|
||||
),
|
||||
);
|
||||
|
||||
final updatePinBtn = Container(
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.primaryColor,
|
||||
),
|
||||
child: MaterialButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Theme.LoginColors.loginGradientEnd,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 42.0),
|
||||
child: LocalText(
|
||||
context,
|
||||
'pin.add_btn',
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
onPressed: () => _change(context)),
|
||||
);
|
||||
|
||||
final clearPinBtn = Container(
|
||||
// margin: EdgeInsets.only(top: 320.0),
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.primaryColor,
|
||||
),
|
||||
child: MaterialButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Theme.LoginColors.loginGradientEnd,
|
||||
//shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 10.0, horizontal: 42.0),
|
||||
child: LocalText(
|
||||
context,
|
||||
'pin.clear_btn',
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
onPressed: () => _clear(context)),
|
||||
);
|
||||
return Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Card(
|
||||
elevation: 2.0,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Container(
|
||||
width: 300.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
switchBtnBox,
|
||||
isSwitched ? Container() : pinInputBox,
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
passwordInputBox
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
isSwitched ? Container() : updatePinBtn,
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
!isSwitched ? Container() : clearPinBtn
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleSignup() {
|
||||
setState(() {
|
||||
_obscureTextSignup = !_obscureTextSignup;
|
||||
});
|
||||
}
|
||||
|
||||
void _change(BuildContext context) async {
|
||||
if (!formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
try {
|
||||
await userModel.updatePin(_pinController.text, _passwordController.text);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _clear(BuildContext context) async {
|
||||
if (!formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
try {
|
||||
await userModel.clearPin(_passwordController.text);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _validatePassword(value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("login.password_empty");
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return AppTranslations.of(context).text("login.password_size");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _validatePinCode(value) {
|
||||
if (!isSwitched) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("change.pin_empty");
|
||||
}
|
||||
if (value.length < 6 || value.length > 6) {
|
||||
return AppTranslations.of(context).text("change.pin_size");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
184
lib/pages/announcement.dart
Normal file
184
lib/pages/announcement.dart
Normal file
@@ -0,0 +1,184 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/announcement_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/vo/announcement.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/popupmenu.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
import 'package:zefyr/zefyr.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
import 'announcement_editor.dart';
|
||||
|
||||
class AnnouncementPage extends StatefulWidget {
|
||||
final Announcement announcement;
|
||||
|
||||
const AnnouncementPage({Key key, this.announcement}) : super(key: key);
|
||||
@override
|
||||
_AnnouncementState createState() => _AnnouncementState();
|
||||
}
|
||||
|
||||
class _AnnouncementState extends State<AnnouncementPage> {
|
||||
ZefyrController _textController;
|
||||
|
||||
TextEditingController nameController = new TextEditingController();
|
||||
FocusNode _focusNode;
|
||||
NotusDocument document = new NotusDocument();
|
||||
bool isLoading = false;
|
||||
|
||||
Announcement _announcement = new Announcement();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.announcement != null) {
|
||||
_announcement = widget.announcement;
|
||||
nameController.text = _announcement.name;
|
||||
_textController = ZefyrController(_loadDocument(_announcement));
|
||||
_focusNode = FocusNode();
|
||||
}
|
||||
}
|
||||
|
||||
NotusDocument _loadDocument(Announcement announcement) {
|
||||
NotusDocument doc;
|
||||
try {
|
||||
doc = NotusDocument.fromJson(jsonDecode(announcement.text));
|
||||
} catch (e) {}
|
||||
if (doc == null) {
|
||||
doc = NotusDocument();
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
|
||||
bool isOwnerAndAbove =
|
||||
mainModel.user != null && mainModel.user.isOwnerAndAbove();
|
||||
bool hasAdmin = mainModel.user != null && mainModel.user.hasAdmin();
|
||||
|
||||
final nameBox = Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
LocalText(context, 'announcement.name'),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
nameController.text,
|
||||
style: textStyle,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final textBox = Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 5, right: 20),
|
||||
child: ZefyrTheme(
|
||||
data: ZefyrThemeData().copyWith(),
|
||||
child: ZefyrScaffold(
|
||||
child: ZefyrEditor(
|
||||
mode: ZefyrMode.view,
|
||||
padding: EdgeInsets.all(16),
|
||||
controller: _textController,
|
||||
focusNode: _focusNode,
|
||||
),
|
||||
)),
|
||||
));
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: LocalText(context, 'announcement.form.title',
|
||||
color: Colors.white, fontSize: 20),
|
||||
backgroundColor: primaryColor,
|
||||
actions: <Widget>[
|
||||
isOwnerAndAbove || hasAdmin
|
||||
? PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
tooltip: 'This is tooltip',
|
||||
onSelected: _select,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return announcementMenu.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
value: choice,
|
||||
child: Text(choice.status),
|
||||
);
|
||||
}).toList();
|
||||
})
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
nameBox,
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10),
|
||||
child: Divider(
|
||||
color: Colors.grey,
|
||||
)),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 20),
|
||||
child: LocalText(context, 'announcement.desc')),
|
||||
textBox,
|
||||
SizedBox(height: 10)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _select(PopupMenu choice) async {
|
||||
if (choice.index == 1) {
|
||||
Announcement _anno = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AnnouncementEditor(announcement: _announcement)),
|
||||
);
|
||||
if (_anno == null) return;
|
||||
|
||||
setState(() {
|
||||
_announcement = _anno;
|
||||
nameController.text = _announcement.name;
|
||||
_textController = ZefyrController(_loadDocument(_announcement));
|
||||
_focusNode = FocusNode();
|
||||
});
|
||||
} else if (choice.index == 2) {
|
||||
showConfirmDialog(context, "announcement.delete_confirm", () {
|
||||
_delete(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _delete(BuildContext context) async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
try {
|
||||
if (widget.announcement != null) {
|
||||
await Provider.of<AnnouncementModel>(context)
|
||||
.deleteAnnouncement(widget.announcement);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
152
lib/pages/announcement_editor.dart
Normal file
152
lib/pages/announcement_editor.dart
Normal file
@@ -0,0 +1,152 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/announcement_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/announcement.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
import 'package:zefyr/zefyr.dart';
|
||||
|
||||
class AnnouncementEditor extends StatefulWidget {
|
||||
final Announcement announcement;
|
||||
|
||||
const AnnouncementEditor({Key key, this.announcement}) : super(key: key);
|
||||
@override
|
||||
_AnnouncementEditorState createState() => _AnnouncementEditorState();
|
||||
}
|
||||
|
||||
class _AnnouncementEditorState extends State<AnnouncementEditor> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
static final _scafoldKey = new GlobalKey<ScaffoldState>();
|
||||
TextEditingController nameController = new TextEditingController();
|
||||
bool _isLoading = false;
|
||||
FocusNode _focusNode;
|
||||
ZefyrController _textController;
|
||||
Announcement _announcement = new Announcement();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.announcement != null) {
|
||||
_announcement = widget.announcement;
|
||||
nameController.text = _announcement.name;
|
||||
NotusDocument doc =
|
||||
NotusDocument.fromJson(jsonDecode(widget.announcement.text));
|
||||
_textController = ZefyrController(doc);
|
||||
_focusNode = FocusNode();
|
||||
} else {
|
||||
_textController = ZefyrController(NotusDocument());
|
||||
_focusNode = FocusNode();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
|
||||
final nameBox = Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: TextFormField(
|
||||
controller: nameController,
|
||||
autofocus: false,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text("announcement.name"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("announcement.name_empty");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final textEditor = ZefyrField(
|
||||
height: 200.0,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppTranslations.of(context).text('announcement.desc'),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
),
|
||||
controller: _textController,
|
||||
focusNode: _focusNode,
|
||||
autofocus: false,
|
||||
physics: ClampingScrollPhysics(),
|
||||
);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
"announcement.form.title",
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.save),
|
||||
onPressed: () {
|
||||
_save(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: ZefyrScaffold(
|
||||
key: _scafoldKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 20.0, right: 20.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
nameBox,
|
||||
SizedBox(height: 10),
|
||||
textEditor,
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _save(BuildContext context) {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
_announcement.name = nameController.text;
|
||||
final contents = jsonEncode(_textController.document);
|
||||
_announcement.text = contents;
|
||||
|
||||
if (widget.announcement != null) {
|
||||
Provider.of<AnnouncementModel>(context, listen: false)
|
||||
.updateAnnouncement(_announcement);
|
||||
} else {
|
||||
Provider.of<AnnouncementModel>(context, listen: false)
|
||||
.createAnnouncement(_announcement);
|
||||
}
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
Future.delayed(const Duration(milliseconds: 3000), () {
|
||||
_isLoading = false;
|
||||
Navigator.pop<Announcement>(context, this._announcement);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
145
lib/pages/announcement_list.dart
Normal file
145
lib/pages/announcement_list.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/announcement_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/vo/announcement.dart' as Announce;
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
import 'announcement.dart';
|
||||
import 'announcement_editor.dart';
|
||||
|
||||
class AnnouncementList extends StatefulWidget {
|
||||
@override
|
||||
_AnnouncementListState createState() => _AnnouncementListState();
|
||||
}
|
||||
|
||||
class _AnnouncementListState extends State<AnnouncementList> {
|
||||
var timeFormatter = new DateFormat('KK:mm a');
|
||||
var dateFormatter = new DateFormat('dd MMM');
|
||||
|
||||
final double dotSize = 15.0;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AnnouncementModel announcementModel =
|
||||
Provider.of<AnnouncementModel>(context);
|
||||
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
bool isOwnerAndAbove =
|
||||
mainModel.user != null && mainModel.user.isOwnerAndAbove();
|
||||
bool hasAdmin = mainModel.user != null && mainModel.user.hasAdmin();
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title:
|
||||
LocalText(
|
||||
context,
|
||||
"announcement.title",
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
)
|
||||
),
|
||||
floatingActionButton: isOwnerAndAbove || hasAdmin
|
||||
? FloatingActionButton(
|
||||
backgroundColor: primaryColor,
|
||||
child: Icon(Icons.add),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AnnouncementEditor()),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
body: new ListView.separated(
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
color: Colors.black,
|
||||
),
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: announcementModel.announcements.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
Announce.Announcement announce =
|
||||
announcementModel.announcements[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AnnouncementPage(announcement: announce)),
|
||||
);
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: new EdgeInsets.symmetric(
|
||||
horizontal: 32.0 - dotSize / 2),
|
||||
child: Icon(
|
||||
Icons.announcement,
|
||||
color: primaryColor,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
announce.name == null
|
||||
? Container()
|
||||
: new Text(
|
||||
announce.name,
|
||||
style: new TextStyle(
|
||||
fontSize: 15.0,
|
||||
color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(announce.time==null?"":timeFormatter.format(announce.time)),
|
||||
),
|
||||
announce.fromToday()
|
||||
? Container()
|
||||
: Text(announce.time==null?"":dateFormatter.format(announce.time)),
|
||||
],
|
||||
)
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
195
lib/pages/banks/bank_edit.dart
Normal file
195
lib/pages/banks/bank_edit.dart
Normal file
@@ -0,0 +1,195 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/bank_account.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/local_text_field.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class BankEdit extends StatefulWidget {
|
||||
final BankAccount bankAccount;
|
||||
|
||||
const BankEdit({Key key, this.bankAccount}) : super(key: key);
|
||||
|
||||
@override
|
||||
_BankEditState createState() => _BankEditState();
|
||||
}
|
||||
|
||||
class _BankEditState extends State<BankEdit> {
|
||||
TextEditingController bankNameController = new TextEditingController();
|
||||
TextEditingController accountNameController = new TextEditingController();
|
||||
TextEditingController accountNumberController = new TextEditingController();
|
||||
|
||||
bool _isLoading;
|
||||
bool _isEdit;
|
||||
File image;
|
||||
BankAccount bankAccount;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isLoading = false;
|
||||
_isEdit = widget.bankAccount != null;
|
||||
bankAccount = BankAccount();
|
||||
if (_isEdit) {
|
||||
bankAccount = widget.bankAccount;
|
||||
bankNameController.text = bankAccount.bankName;
|
||||
accountNameController.text = bankAccount.accountName;
|
||||
accountNumberController.text = bankAccount.accountNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: LocalText(context, 'banks.edit.title',
|
||||
color: Colors.white, fontSize: 20),
|
||||
backgroundColor: primaryColor,
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.save),
|
||||
onPressed: () {
|
||||
_save();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
_delete();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
children: <Widget>[
|
||||
LocalTextField(
|
||||
labelKey: "banks.name",
|
||||
textEditingController: bankNameController),
|
||||
LocalTextField(
|
||||
labelKey: "banks.account.name",
|
||||
textEditingController: accountNameController),
|
||||
LocalTextField(
|
||||
labelKey: "banks.account.number",
|
||||
textEditingController: accountNumberController,
|
||||
textInputType: TextInputType.number,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: Center(
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: primaryColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: image == null
|
||||
? Image.network(
|
||||
_isEdit ? widget.bankAccount.bankLogo : "",
|
||||
height: 80,
|
||||
width: 80,
|
||||
)
|
||||
: Image.file(image),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -10,
|
||||
right: -10,
|
||||
child: IconButton(
|
||||
color: primaryColor,
|
||||
icon: const Icon(
|
||||
Icons.edit,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () async {
|
||||
File _image = await ImagePicker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
maxWidth: 300,
|
||||
maxHeight: 300,
|
||||
imageQuality: 80);
|
||||
if (_image != null) {
|
||||
setState(() {
|
||||
this.image = _image;
|
||||
});
|
||||
}
|
||||
}))
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_save() async {
|
||||
if (!_isEdit && image == null) {
|
||||
showMsgDialog(context, "Error", "Need bank logo!");
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
bankAccount.bankName = bankNameController.text;
|
||||
bankAccount.accountName = accountNameController.text;
|
||||
bankAccount.accountNumber = accountNumberController.text;
|
||||
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
if (_isEdit) {
|
||||
await mainModel.updateBankAccount(bankAccount, image);
|
||||
} else {
|
||||
await mainModel.addBankAccount(bankAccount, image);
|
||||
}
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
return;
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
_delete() async {
|
||||
showConfirmDialog(context, "banks.account.delete.confirmation", () async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
if (_isEdit) {
|
||||
await mainModel.deleteBankAccount(bankAccount);
|
||||
}
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
259
lib/pages/banks/banks.dart
Normal file
259
lib/pages/banks/banks.dart
Normal file
@@ -0,0 +1,259 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/banks/bank_edit.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/bank_account.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class BankAccounts extends StatefulWidget {
|
||||
const BankAccounts({Key key}) : super(key: key);
|
||||
@override
|
||||
_BankAccountsState createState() => _BankAccountsState();
|
||||
}
|
||||
|
||||
class _BankAccountsState extends State<BankAccounts> {
|
||||
bool isLoading = false;
|
||||
bool isEdit = false;
|
||||
|
||||
final TextEditingController bankNameCtl = TextEditingController();
|
||||
final TextEditingController accountNameCtl = TextEditingController();
|
||||
final TextEditingController accountNumberCtl = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
List<BankAccount> bankAccounts = mainModel.setting.bankAccounts;
|
||||
|
||||
bool isOwnerAndAbove =
|
||||
mainModel.user != null && mainModel.user.isOwnerAndAbove();
|
||||
bool hasAdmin = mainModel.user != null && mainModel.user.hasAdmin();
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () {
|
||||
if (isEdit) {
|
||||
setState(() {
|
||||
isEdit = false;
|
||||
});
|
||||
return Future.value(false);
|
||||
}
|
||||
return Future.value(true);
|
||||
},
|
||||
child: LocalProgress(
|
||||
inAsyncCall: isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: !isEdit,
|
||||
title: LocalText(context, 'banks.title',
|
||||
color: Colors.white, fontSize: 20),
|
||||
backgroundColor: primaryColor,
|
||||
actions: <Widget>[
|
||||
(isOwnerAndAbove || hasAdmin)
|
||||
? isEdit
|
||||
? IconButton(
|
||||
icon: Icon(Icons.done),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isEdit = false;
|
||||
});
|
||||
},
|
||||
)
|
||||
: IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
_edit();
|
||||
},
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
floatingActionButton: isEdit
|
||||
? FloatingActionButton(
|
||||
backgroundColor: primaryColor,
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => BankEdit()),
|
||||
);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body: new ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: bankAccounts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _item(context, bankAccounts[index]);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_item(BuildContext context, BankAccount bankAccount) {
|
||||
return InkWell(
|
||||
onTap: isEdit
|
||||
? () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BankEdit(
|
||||
bankAccount: bankAccount,
|
||||
)),
|
||||
)
|
||||
: null,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: primaryColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: Image.network(
|
||||
bankAccount.bankLogo,
|
||||
height: 80,
|
||||
width: 80,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: new Text(
|
||||
bankAccount.bankName,
|
||||
style: new TextStyle(
|
||||
fontSize: 20.0, color: Colors.black),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: new Text(
|
||||
bankAccount.accountName,
|
||||
style:
|
||||
new TextStyle(fontSize: 16.0, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
bankAccount.accountNumber,
|
||||
style: new TextStyle(
|
||||
fontSize: 16.0, color: Colors.grey),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text: bankAccount.accountNumber));
|
||||
_showToast(context, bankAccount.bankName);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 7.0),
|
||||
child: Icon(
|
||||
Icons.content_copy,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _displayEditDialog(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Edit'),
|
||||
content: Column(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
controller: bankNameCtl,
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: InputDecoration(hintText: "Enter Bank Name"),
|
||||
),
|
||||
TextField(
|
||||
controller: accountNameCtl,
|
||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: InputDecoration(hintText: "Enter Account Name"),
|
||||
),
|
||||
TextField(
|
||||
controller: accountNumberCtl,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(hintText: "Enter Account Number"),
|
||||
),
|
||||
IconButton(icon: Icon(Icons.photo_library), onPressed: null)
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: const Text('Ok'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _edit() {
|
||||
setState(() {
|
||||
isEdit = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _showToast(BuildContext context, String bankName) {
|
||||
final scaffold = Scaffold.of(context);
|
||||
scaffold.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('copied "$bankName" account number to clipboard'),
|
||||
backgroundColor: primaryColor,
|
||||
duration: Duration(seconds: 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
120
lib/pages/block_list.dart
Normal file
120
lib/pages/block_list.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/popupmenu.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class BlockList extends StatefulWidget {
|
||||
@override
|
||||
_BlockListState createState() => _BlockListState();
|
||||
}
|
||||
|
||||
class _BlockListState extends State<BlockList> {
|
||||
final double dotSize = 15.0;
|
||||
bool _isLoading = false;
|
||||
PopupMenu selectedChoices = blocklistpopup[0];
|
||||
User user = new User();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var userModel = Provider.of<UserModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(context, 'user.block_list',
|
||||
color: Colors.white, fontSize: 20),
|
||||
),
|
||||
body: new ListView.builder(
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15, bottom: 10),
|
||||
shrinkWrap: true,
|
||||
itemCount: userModel.getBlockListUsers().length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Card(
|
||||
elevation: 10,
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7.0),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: new EdgeInsets.symmetric(
|
||||
horizontal: 32.0 - dotSize / 2),
|
||||
child: Image.asset(
|
||||
'assets/block.png',
|
||||
width: 50,
|
||||
height: 50,
|
||||
color: primaryColor,
|
||||
)),
|
||||
new Text(
|
||||
userModel.getBlockListUsers()[index].name ==
|
||||
null
|
||||
? ""
|
||||
: userModel.getBlockListUsers()[index].name,
|
||||
style: new TextStyle(
|
||||
fontSize: 17.0, color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
tooltip: 'This is tooltip',
|
||||
onSelected: _select,
|
||||
itemBuilder: (BuildContext context) {
|
||||
this.user = userModel.getBlockListUsers()[index];
|
||||
return blocklistpopup.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
value: choice,
|
||||
child: Text(choice.status),
|
||||
);
|
||||
}).toList();
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _select(PopupMenu choice) async {
|
||||
selectedChoices = choice;
|
||||
if (choice.index == 1) {
|
||||
showConfirmDialog(context, "user.unblock.confirm", () {
|
||||
_unblock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_unblock() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
var userModel = Provider.of<UserModel>(context);
|
||||
await userModel.unblockPhone(this.user.phoneNumber);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
281
lib/pages/buyer_info.dart
Normal file
281
lib/pages/buyer_info.dart
Normal file
@@ -0,0 +1,281 @@
|
||||
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/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';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
import 'document_log_page.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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
208
lib/pages/buyer_list.dart
Normal file
208
lib/pages/buyer_list.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/buyer_model.dart';
|
||||
import 'package:fcs/pages/search_page.dart';
|
||||
import 'package:fcs/vo/buyer.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/popupmenu.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
import 'buyer_list_row.dart';
|
||||
|
||||
class BuyerList extends StatefulWidget {
|
||||
@override
|
||||
_BuyerListState createState() => _BuyerListState();
|
||||
}
|
||||
|
||||
class _BuyerListState extends State<BuyerList> {
|
||||
Buyer buyer;
|
||||
int _selectedIndex = 0;
|
||||
bool _isLoading = false;
|
||||
int _selectedSortIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var buyerModel = Provider.of<BuyerModel>(context, listen: false);
|
||||
var index = buyerModel.popupMenu.index;
|
||||
_selectedIndex = index;
|
||||
var sortIndexndex = buyerModel.sortMenu.index;
|
||||
_selectedSortIndex = sortIndexndex;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
BuyerModel buyerModel = Provider.of<BuyerModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("buyer.title")),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.search,
|
||||
color: Colors.white,
|
||||
),
|
||||
iconSize: 30,
|
||||
onPressed: () => showPlacesSearch(context),
|
||||
),
|
||||
PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
this._selectedSortIndex = selected.index;
|
||||
this._selectedIndex = 0;
|
||||
buyerModel.filterSorting(
|
||||
_selectedSortIndex, this._selectedIndex);
|
||||
});
|
||||
},
|
||||
icon: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.sort,
|
||||
color: primaryColor,
|
||||
),
|
||||
_selectedSortIndex != null
|
||||
? Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
)),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return userMenu.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
value: choice,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(choice.status),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
_selectedSortIndex != null &&
|
||||
_selectedSortIndex == choice.index
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.grey,
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
onSelected: (selected) {
|
||||
String status;
|
||||
setState(() {
|
||||
this._selectedIndex = selected.index;
|
||||
this._selectedSortIndex = null;
|
||||
if (selected.status == 'All') {
|
||||
status = null;
|
||||
} else {
|
||||
status = selected.status;
|
||||
}
|
||||
buyerModel.filterStatus(
|
||||
status, _selectedIndex, this._selectedSortIndex);
|
||||
});
|
||||
},
|
||||
icon: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.filter_list,
|
||||
color: primaryColor,
|
||||
),
|
||||
_selectedIndex != 0
|
||||
? Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
)),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return buyerStatusMenu.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
value: choice,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(choice.status),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
_selectedIndex != null &&
|
||||
_selectedIndex == choice.index
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.grey,
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
],
|
||||
),
|
||||
body: new ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: buyerModel.buyers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return BuyerListRow(
|
||||
buyer: buyerModel.buyers[index],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
106
lib/pages/buyer_list_row.dart
Normal file
106
lib/pages/buyer_list_row.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/buyer_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/buyer.dart';
|
||||
|
||||
import 'buyer_info.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),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
272
lib/pages/chage_phone_number.dart
Normal file
272
lib/pages/chage_phone_number.dart
Normal file
@@ -0,0 +1,272 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart' as Theme;
|
||||
import 'confirm_email.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class ChangePhoneNumber extends StatefulWidget {
|
||||
final User user;
|
||||
ChangePhoneNumber(
|
||||
this.user, {
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ChangePhoneNumberState createState() => new _ChangePhoneNumberState();
|
||||
}
|
||||
|
||||
class _ChangePhoneNumberState extends State<ChangePhoneNumber>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
final FocusNode myFocusNodePhone = FocusNode();
|
||||
final FocusNode myFocusNodenewPhone = FocusNode();
|
||||
|
||||
TextEditingController _phoneController = new TextEditingController();
|
||||
TextEditingController _newPhoneController = new TextEditingController();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height >= 775.0
|
||||
? MediaQuery.of(context).size.height
|
||||
: 580.0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 35.0, bottom: 10),
|
||||
child: ListTile(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: LocalText(
|
||||
context,
|
||||
'change.phone',
|
||||
color: Colors.black87,
|
||||
fontSize: 17,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PageView(
|
||||
children: <Widget>[
|
||||
new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: _buildReset(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myFocusNodenewPhone.dispose();
|
||||
myFocusNodePhone.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_phoneController.text = widget.user.phone;
|
||||
_newPhoneController.text = "09";
|
||||
|
||||
// SystemChrome.setPreferredOrientations([
|
||||
// DeviceOrientation.portraitUp,
|
||||
// DeviceOrientation.portraitDown,
|
||||
// ]);
|
||||
}
|
||||
|
||||
Widget _buildReset(BuildContext context) {
|
||||
return Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Card(
|
||||
elevation: 2.0,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Container(
|
||||
width: 300.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodePhone,
|
||||
controller: _phoneController,
|
||||
readOnly: true,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.phone,
|
||||
color: Colors.black,
|
||||
size: 22.0,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.phone"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodenewPhone,
|
||||
controller: _newPhoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.phone,
|
||||
color: Colors.black,
|
||||
size: 22.0,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("change.new.phone"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
),
|
||||
validator: _validatePhone,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Container(
|
||||
// margin: EdgeInsets.only(top: 320.0),
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.primaryColor,
|
||||
),
|
||||
child: MaterialButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Theme.LoginColors.loginGradientEnd,
|
||||
//shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0, horizontal: 42.0),
|
||||
child: LocalText(
|
||||
context,
|
||||
'change',
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
onPressed: () => _change(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _change(BuildContext context) async {
|
||||
if (!formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
var _phone = _newPhoneController.text;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
try {
|
||||
await userModel.changePhone(widget.user.phoneNumber, _phone);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ConfirmEmail(
|
||||
id: widget.user.phoneNumber,
|
||||
phoneNumber: _phone,
|
||||
)));
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _validatePhone(value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("change.phone_empty");
|
||||
}
|
||||
if (!value.startsWith("09")) {
|
||||
return 'Only "09".';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
320
lib/pages/change_password.dart
Normal file
320
lib/pages/change_password.dart
Normal file
@@ -0,0 +1,320 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart' as Theme;
|
||||
import 'util.dart';
|
||||
|
||||
class ChangePassword extends StatefulWidget {
|
||||
final User user;
|
||||
ChangePassword(
|
||||
this.user, {
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ChangePasswordState createState() => new _ChangePasswordState();
|
||||
}
|
||||
|
||||
class _ChangePasswordState extends State<ChangePassword>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
final FocusNode myFocusNodePassword = FocusNode();
|
||||
final FocusNode myFocusNodeEmail = FocusNode();
|
||||
|
||||
bool _obscureTextLogin = true;
|
||||
bool _obscureTextSignup = true;
|
||||
bool _obscureTextSignupConfirm = true;
|
||||
|
||||
TextEditingController _smsController = new TextEditingController();
|
||||
TextEditingController _passwordController = new TextEditingController();
|
||||
TextEditingController _confirmPasswordController =
|
||||
new TextEditingController();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height >= 775.0
|
||||
? MediaQuery.of(context).size.height
|
||||
: 580.0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 35.0, bottom: 10),
|
||||
child: ListTile(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: LocalText(
|
||||
context,
|
||||
'change.password.title',
|
||||
color: Colors.black87,
|
||||
fontSize: 17,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PageView(
|
||||
children: <Widget>[
|
||||
new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: _buildReset(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myFocusNodePassword.dispose();
|
||||
myFocusNodeEmail.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// SystemChrome.setPreferredOrientations([
|
||||
// DeviceOrientation.portraitUp,
|
||||
// DeviceOrientation.portraitDown,
|
||||
// ]);
|
||||
|
||||
_smsController.text = "";
|
||||
}
|
||||
|
||||
Widget _buildReset(BuildContext context) {
|
||||
return Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Card(
|
||||
elevation: 2.0,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Container(
|
||||
width: 300.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodePassword,
|
||||
controller: _passwordController,
|
||||
obscureText: _obscureTextSignup,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.lock,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.password"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: _toggleSignup,
|
||||
child: Icon(
|
||||
_obscureTextSignup
|
||||
? FontAwesomeIcons.eye
|
||||
: FontAwesomeIcons.eyeSlash,
|
||||
size: 15.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: _validatePassword,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
controller: _confirmPasswordController,
|
||||
obscureText: _obscureTextSignupConfirm,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.lock,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.confirm_password"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: _toggleSignupConfirm,
|
||||
child: Icon(
|
||||
_obscureTextSignupConfirm
|
||||
? FontAwesomeIcons.eye
|
||||
: FontAwesomeIcons.eyeSlash,
|
||||
size: 15.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: _validateConfirmPassword,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Container(
|
||||
// margin: EdgeInsets.only(top: 320.0),
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.primaryColor,
|
||||
),
|
||||
child: MaterialButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Theme.LoginColors.loginGradientEnd,
|
||||
//shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0, horizontal: 42.0),
|
||||
child: LocalText(
|
||||
context,
|
||||
'change',
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
onPressed: () => _change(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleLogin() {
|
||||
setState(() {
|
||||
_obscureTextLogin = !_obscureTextLogin;
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleSignup() {
|
||||
setState(() {
|
||||
_obscureTextSignup = !_obscureTextSignup;
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleSignupConfirm() {
|
||||
setState(() {
|
||||
_obscureTextSignupConfirm = !_obscureTextSignupConfirm;
|
||||
});
|
||||
}
|
||||
|
||||
void _change(BuildContext context) async {
|
||||
if (!formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
var password = _passwordController.text;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
try {
|
||||
await userModel.changePassword(widget.user.phoneNumber, password);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _validatePassword(value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("login.password_empty");
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return AppTranslations.of(context).text("login.password_size");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _validateConfirmPassword(value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("login.password_empty");
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return AppTranslations.of(context).text("login.password_size");
|
||||
}
|
||||
if (value != _passwordController.text) {
|
||||
return AppTranslations.of(context).text("login.password_mismatch");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
114
lib/pages/confirm_email.dart
Normal file
114
lib/pages/confirm_email.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
class ConfirmEmail extends StatefulWidget {
|
||||
final String id, email, phoneNumber;
|
||||
|
||||
const ConfirmEmail({Key key, this.id, this.email, this.phoneNumber})
|
||||
: super(key: key);
|
||||
@override
|
||||
_ConfirmEmailState createState() => _ConfirmEmailState();
|
||||
}
|
||||
|
||||
class _ConfirmEmailState extends State<ConfirmEmail> {
|
||||
final TextEditingController _sms = new TextEditingController();
|
||||
bool _isLoading = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
_confimEmail() async {
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
await userModel.confirmEmail(
|
||||
widget.id, widget.email, widget.phoneNumber, _sms.text);
|
||||
Navigator.pushNamedAndRemoveUntil(context, "/home", (r) => false);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
}
|
||||
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final smsInput = TextFormField(
|
||||
controller: _sms,
|
||||
keyboardType: TextInputType.number,
|
||||
autofocus: false,
|
||||
decoration: new InputDecoration(
|
||||
labelText: widget.email == null
|
||||
? AppTranslations.of(context).text("sms.sms")
|
||||
: AppTranslations.of(context).text("email.code"),
|
||||
labelStyle: labelStyle,
|
||||
hintText: 'eg. 123456',
|
||||
icon: Icon(
|
||||
Icons.lock,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return widget.email == null
|
||||
? AppTranslations.of(context).text("sms.empty")
|
||||
: AppTranslations.of(context).text("email.code_empty");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final enterButton = Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: RaisedButton(
|
||||
onPressed: () => _confimEmail(),
|
||||
padding: EdgeInsets.all(12),
|
||||
color: primaryColor,
|
||||
child: Text(AppTranslations.of(context).text("sms.enter"),
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
);
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.email == null
|
||||
? AppTranslations.of(context).text("input_sms")
|
||||
: AppTranslations.of(context).text("email.input")),
|
||||
backgroundColor: primaryColor,
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
children: <Widget>[
|
||||
Form(key: _formKey, child: smsInput),
|
||||
SizedBox(height: 8.0),
|
||||
enterButton,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
161
lib/pages/contact.dart
Normal file
161
lib/pages/contact.dart
Normal file
@@ -0,0 +1,161 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'contact_editor.dart';
|
||||
|
||||
class Contact extends StatefulWidget {
|
||||
@override
|
||||
_ContactState createState() => _ContactState();
|
||||
}
|
||||
|
||||
class _ContactState extends State<Contact> {
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
bool isOwner = mainModel.user != null && mainModel.user.isOwner();
|
||||
bool hasAdmin = mainModel.user != null && mainModel.user.hasAdmin();
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.grey,
|
||||
),
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: Image(
|
||||
height: 30,
|
||||
fit: BoxFit.scaleDown,
|
||||
image: new AssetImage('assets/img/logo.png')),
|
||||
actions: <Widget>[
|
||||
isOwner || hasAdmin
|
||||
? IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ContactEditor(setting: mainModel.setting)),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 5.0, bottom: 5),
|
||||
child: LocalText(
|
||||
context,
|
||||
"contact.title",
|
||||
fontSize: 25,
|
||||
)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:
|
||||
List.generate(mainModel.setting.phones.length, (index) {
|
||||
return link(mainModel.setting.phones[index], Icons.phone,
|
||||
onTap: () => _call(mainModel.setting.phones[index]));
|
||||
}),
|
||||
),
|
||||
),
|
||||
link(mainModel.setting.deliveryPhone, Icons.phone_forwarded,
|
||||
onTap: () => _call(mainModel.setting.deliveryPhone),
|
||||
label: LocalText(context, "contact.delivery.phone")),
|
||||
link(mainModel.setting.email, Icons.email,
|
||||
onTap: () => _email(mainModel.setting.email)),
|
||||
link(mainModel.setting.facebook, FontAwesomeIcons.facebook,
|
||||
onTap: () => _openLink(mainModel.setting.facebook)),
|
||||
link(mainModel.setting.website, FontAwesomeIcons.chrome,
|
||||
onTap: () => _openLink(mainModel.setting.website)),
|
||||
link(mainModel.setting.address, Icons.location_on),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget link(String text, IconData iconData,
|
||||
{Function() onTap, Widget label}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 18.0, bottom: 5),
|
||||
child: InkWell(
|
||||
onTap: () => onTap != null ? onTap() : null,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
iconData,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
label == null ? Container() : label,
|
||||
Text(
|
||||
text == null ? "" : text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
onTap == null
|
||||
? Container()
|
||||
: Icon(
|
||||
Icons.open_in_new,
|
||||
color: Colors.grey,
|
||||
size: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> getVersionNumber() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
String version = packageInfo.version + "+" + packageInfo.buildNumber;
|
||||
return version;
|
||||
}
|
||||
|
||||
_call(String phone) {
|
||||
showConfirmDialog(
|
||||
context, "contact.phone.confim", () => launch("tel:$phone"),
|
||||
translationVariables: ["$phone"]);
|
||||
}
|
||||
|
||||
_email(String email) {
|
||||
showConfirmDialog(
|
||||
context, "contact.email.configm", () => launch("mailto:$email"),
|
||||
translationVariables: ["$email"]);
|
||||
}
|
||||
|
||||
_openLink(String link) {
|
||||
showConfirmDialog(context, "contact.open.confrim", () => launch("$link"),
|
||||
translationVariables: ["$link"]);
|
||||
}
|
||||
}
|
||||
274
lib/pages/contact_editor.dart
Normal file
274
lib/pages/contact_editor.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/phone_input.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/setting.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/local_text_field.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class ContactEditor extends StatefulWidget {
|
||||
final Setting setting;
|
||||
const ContactEditor({this.setting});
|
||||
@override
|
||||
_ContactEditorState createState() => _ContactEditorState();
|
||||
}
|
||||
|
||||
class _ContactEditorState extends State<ContactEditor> {
|
||||
TextEditingController _email = new TextEditingController();
|
||||
TextEditingController _facebook = new TextEditingController();
|
||||
TextEditingController _website = new TextEditingController();
|
||||
TextEditingController _address = new TextEditingController();
|
||||
TextEditingController _deliveryPhone = new TextEditingController();
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
|
||||
List<String> phones = new List();
|
||||
List<String> _initPhones = new List();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.setting != null) {
|
||||
this._initPhones = widget.setting.phones;
|
||||
_email.text = widget.setting.email;
|
||||
_facebook.text = widget.setting.facebook;
|
||||
_website.text = widget.setting.website;
|
||||
_deliveryPhone.text = widget.setting.deliveryPhone;
|
||||
_address.text = widget.setting.address;
|
||||
|
||||
phones.clear();
|
||||
_initPhones.forEach((p) {
|
||||
phones.add(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
|
||||
final emailBox = TextFormField(
|
||||
controller: _email,
|
||||
autofocus: false,
|
||||
cursorColor: primaryColor,
|
||||
style: textStyle,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text('contact.email'),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
icon: Icon(
|
||||
Icons.email,
|
||||
color: primaryColor,
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text('contact.email.empty');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final faceBookBox = TextFormField(
|
||||
controller: _facebook,
|
||||
autofocus: false,
|
||||
cursorColor: primaryColor,
|
||||
style: textStyle,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text('contact.facebook'),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.facebook,
|
||||
color: primaryColor,
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text('contact.facebook.empty');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final googleBox = TextFormField(
|
||||
controller: _website,
|
||||
autofocus: false,
|
||||
cursorColor: primaryColor,
|
||||
style: textStyle,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text('contact.google'),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.chrome,
|
||||
color: primaryColor,
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text('contact.google.empty');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final addPhoneNumber = ListTile(
|
||||
contentPadding: EdgeInsets.only(top: 15),
|
||||
title: ButtonTheme(
|
||||
height: 45,
|
||||
child: RaisedButton(
|
||||
color: Colors.white,
|
||||
onPressed: () async {
|
||||
var phone = await showDialog(
|
||||
context: context, builder: (_) => PhoneEditor());
|
||||
_save(phone);
|
||||
},
|
||||
child: Text("Add Phone",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
)),
|
||||
),
|
||||
));
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
"contact.title",
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.send),
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
showConfirmDialog(context, "contact.confrim", () {
|
||||
_submit();
|
||||
});
|
||||
})
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
children: <Widget>[
|
||||
this.phones.isNotEmpty
|
||||
? ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: 1000),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
return Stack(
|
||||
alignment: const Alignment(1.0, 1.0),
|
||||
children: <Widget>[
|
||||
new TextField(
|
||||
controller: new TextEditingController(
|
||||
text: this.phones[index]),
|
||||
cursorColor: primaryColor,
|
||||
readOnly: true,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
icon: Icon(
|
||||
Icons.phone,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
new FlatButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
this.phones.remove(this.phones[index]);
|
||||
});
|
||||
},
|
||||
child: new Icon(
|
||||
Icons.cancel,
|
||||
size: 25,
|
||||
))
|
||||
],
|
||||
);
|
||||
},
|
||||
itemCount: this.phones.length,
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
addPhoneNumber,
|
||||
LocalTextField(
|
||||
textEditingController: _deliveryPhone,
|
||||
icon: Icon(
|
||||
Icons.phone_forwarded,
|
||||
color: primaryColor,
|
||||
),
|
||||
labelKey: "contact.delivery.phone",
|
||||
),
|
||||
emailBox,
|
||||
faceBookBox,
|
||||
googleBox,
|
||||
LocalTextField(
|
||||
textEditingController: _address,
|
||||
icon: Icon(
|
||||
Icons.location_on,
|
||||
color: primaryColor,
|
||||
),
|
||||
labelKey: "contact.address",
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
_save(String phone) {
|
||||
if (phone == null) return;
|
||||
setState(() {
|
||||
this.phones.add(phone);
|
||||
});
|
||||
}
|
||||
|
||||
_submit() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
widget.setting.email = _email.text;
|
||||
widget.setting.facebook = _facebook.text;
|
||||
widget.setting.website = _website.text;
|
||||
widget.setting.phones = this.phones;
|
||||
widget.setting.address = _address.text;
|
||||
widget.setting.deliveryPhone = _deliveryPhone.text;
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
await mainModel.updateContact(widget.setting);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
521
lib/pages/delivery/delivery_item.dart
Normal file
521
lib/pages/delivery/delivery_item.dart
Normal file
@@ -0,0 +1,521 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:quiver/async.dart';
|
||||
import 'package:fcs/model/do_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/po_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/pages/do/photo_page.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/widget/img_file.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
class DeliveryItem extends StatefulWidget {
|
||||
final DOSubmission doSubmission;
|
||||
const DeliveryItem({this.doSubmission});
|
||||
@override
|
||||
_DeliveryItemState createState() => _DeliveryItemState();
|
||||
}
|
||||
|
||||
class _DeliveryItemState extends State<DeliveryItem> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
bool _isLoading = false;
|
||||
TextEditingController _date = new TextEditingController();
|
||||
TextEditingController _number = new TextEditingController();
|
||||
TextEditingController _licence = new TextEditingController();
|
||||
TextEditingController _driver = new TextEditingController();
|
||||
TextEditingController _carNo = new TextEditingController();
|
||||
TextEditingController _type = new TextEditingController();
|
||||
TextEditingController _name = new TextEditingController();
|
||||
TextEditingController _bizName = new TextEditingController();
|
||||
TextEditingController _storage = new TextEditingController();
|
||||
TextEditingController _comment = new TextEditingController();
|
||||
DOSubmission doObj = DOSubmission();
|
||||
int _count;
|
||||
DateTime _result;
|
||||
File storageChargeFile;
|
||||
File receiptImageFile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
var mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
var doModel = Provider.of<DOModel>(context, listen: false);
|
||||
|
||||
doObj = widget.doSubmission;
|
||||
_date.text = doObj.deliveryDate != null
|
||||
? dateFormatter.format(doObj.deliveryDate)
|
||||
: "";
|
||||
_number.text = doObj.doNumber.toString();
|
||||
_licence.text = doObj.driverLicenseNumber;
|
||||
_driver.text = doObj.driverName;
|
||||
_carNo.text = doObj.carNo;
|
||||
_type.text = doObj.type;
|
||||
_name.text = doObj.userName;
|
||||
_bizName.text = doObj.bizName;
|
||||
_storage.text = doObj.storageCharge == null
|
||||
? ""
|
||||
: numberFormatter.format(doObj.storageCharge);
|
||||
_comment.text = doObj.comment;
|
||||
|
||||
if (doObj.deliveryStatus == 'initiated') {
|
||||
_count = doModel.timber;
|
||||
Duration diff = DateTime.now().difference(doObj.deliveryInitiatedTime);
|
||||
|
||||
if (diff.inMinutes < mainModel.setting.deliveryStartWaitMin) {
|
||||
var time = mainModel.setting.deliveryStartWaitMin - diff.inMinutes;
|
||||
new CountdownTimer(
|
||||
new Duration(minutes: time), new Duration(seconds: 1))
|
||||
.listen((data) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_count = data.remaining.inSeconds;
|
||||
doModel.addTimber(_count);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_count = 0;
|
||||
}
|
||||
_load();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
String formattedTime;
|
||||
if (doObj.deliveryStatus == 'initiated') {
|
||||
_result = DateTime(
|
||||
doObj.deliveryInitiatedTime.year,
|
||||
doObj.deliveryInitiatedTime.month,
|
||||
doObj.deliveryInitiatedTime.day,
|
||||
doObj.deliveryInitiatedTime.hour,
|
||||
doObj.deliveryInitiatedTime.minute,
|
||||
_count);
|
||||
formattedTime = DateFormat.ms().format(_result);
|
||||
}
|
||||
|
||||
final dateBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 15),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.date"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_date.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final numberBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.do_num"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_number.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final driverBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.driver"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_driver.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final carNoBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.car"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_carNo.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final licenceBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.licence"),
|
||||
ImageFile(
|
||||
enabled: false,
|
||||
title: "Image",
|
||||
initialImgUrl: doObj.driverLicenceUrl,
|
||||
onFile: (file) {}),
|
||||
],
|
||||
),
|
||||
);
|
||||
final statusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.status"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doObj.status,
|
||||
style: doObj.isPending
|
||||
? textHighlightBlueStyle
|
||||
: doObj.isApproved
|
||||
? textHighlightGreenStyle
|
||||
: textHighlightRedStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final deliveryStatusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.delivery.status"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 15),
|
||||
child: Text(
|
||||
doObj.getDeliveryStatus,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
doObj.deliveryStatus == 'initiated'
|
||||
? Text(
|
||||
"(can start in $formattedTime)",
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final typeBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.type"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Text(
|
||||
_type.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final userNameBox = Container(
|
||||
padding: EdgeInsets.only(top: 5, left: 20),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.name"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Text(
|
||||
_name.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final bizNameBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.biz"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Text(
|
||||
_bizName.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final receiptImagebox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 0),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.receipt"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: ImageFile(
|
||||
enabled: true,
|
||||
initialImgUrl: doObj.doReceiptUrl,
|
||||
title: "Receipt File",
|
||||
onFile: (file) {
|
||||
this.receiptImageFile = file;
|
||||
},
|
||||
),
|
||||
),
|
||||
]));
|
||||
|
||||
final storageBox = Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.storage_charge"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_storage.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final storagePaymentBox = Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.storage_receipt"),
|
||||
ImageFile(
|
||||
enabled: false,
|
||||
title: "Receipt File",
|
||||
initialImgUrl: this.doObj.storageReceiptUrl,
|
||||
onFile: (file) {
|
||||
this.storageChargeFile = file;
|
||||
}),
|
||||
]));
|
||||
|
||||
final commentBox = Container(
|
||||
padding: EdgeInsets.only(top: 5, left: 20),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.comment"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_comment.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("delivery"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
|
||||
actions: <Widget>[
|
||||
isBuyer
|
||||
? Container()
|
||||
: PopupMenuButton(
|
||||
onSelected: _select,
|
||||
itemBuilder: (context) => List<PopupMenuEntry>.from([
|
||||
PopupMenuItem(
|
||||
enabled: this.doObj.isApproved,
|
||||
value: 5,
|
||||
child: Text("End Delivery"),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10),
|
||||
child: Card(
|
||||
elevation: 23,
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
dateBox,
|
||||
Divider(),
|
||||
numberBox,
|
||||
Divider(),
|
||||
userNameBox,
|
||||
Divider(),
|
||||
bizNameBox,
|
||||
Divider(),
|
||||
typeBox,
|
||||
Divider(),
|
||||
statusBox,
|
||||
Divider(),
|
||||
doObj.comment == null || doObj.comment == ''
|
||||
? Container()
|
||||
: commentBox,
|
||||
doObj.comment == null || doObj.comment == ''
|
||||
? Container()
|
||||
: Divider(),
|
||||
driverBox,
|
||||
Divider(),
|
||||
carNoBox,
|
||||
Divider(),
|
||||
licenceBox,
|
||||
Divider(),
|
||||
receiptImagebox,
|
||||
Divider(),
|
||||
doObj.hasStorageCharge() ? storageBox : Container(),
|
||||
doObj.hasStorageCharge() ? Divider() : Container(),
|
||||
doObj.hasStorageCharge()
|
||||
? storagePaymentBox
|
||||
: Container(),
|
||||
doObj.isApproved || doObj.isClosed
|
||||
? deliveryStatusBox
|
||||
: Container(),
|
||||
doObj.isApproved || doObj.isClosed
|
||||
? Divider()
|
||||
: Container(),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 40,
|
||||
columns: [
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.product"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.storage"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.quantity"),numeric: true
|
||||
),
|
||||
],
|
||||
rows: getProductRow(doObj.doLines),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<DOLine> doLines) {
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
if (doLines.isNotEmpty) {
|
||||
doLines.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doLines.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
}
|
||||
return doLines.map((d) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(d.storageName, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.qty)
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_select(s) {
|
||||
if (s == 5) {
|
||||
if (receiptImageFile == null) {
|
||||
showMsgDialog(context, "Error", "Please insert delivery receipt file");
|
||||
return;
|
||||
}
|
||||
showConfirmDialog(context, "delivery.confirm", () {
|
||||
_endDelivery(receiptImageFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
POSubmissionModel poModel =
|
||||
Provider.of<POSubmissionModel>(context, listen: false);
|
||||
var _doSub = await poModel.loadDOLines(doObj);
|
||||
setState(() {
|
||||
doObj.doLines = _doSub.doLines;
|
||||
});
|
||||
}
|
||||
|
||||
_endDelivery(dynamic receiptFile) async {
|
||||
Uint8List bytesPhoto = receiptFile.readAsBytesSync() as Uint8List;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.endDelivery(doObj, bytesPhoto);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
274
lib/pages/delivery/delivery_list.dart
Normal file
274
lib/pages/delivery/delivery_list.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/delivery_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/delivery/delivery_item.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/popupmenu.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DeliveryList extends StatefulWidget {
|
||||
@override
|
||||
_DeliveryListState createState() => _DeliveryListState();
|
||||
}
|
||||
|
||||
class _DeliveryListState extends State<DeliveryList> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
final double dotSize = 10.0;
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
String status;
|
||||
int _selectedIndex = 0;
|
||||
int _dateIndex = 0;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
var doModel = Provider.of<DeliveryModel>(context, listen: false);
|
||||
_selectedIndex = doModel.popupMenu.index;
|
||||
_dateIndex = doModel.dateIndex;
|
||||
_selectedDate = doModel.selectedDate;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<Null> _selectDate(BuildContext context) async {
|
||||
var deliveryModel = Provider.of<DeliveryModel>(context);
|
||||
|
||||
final DateTime picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(2101),
|
||||
builder: (BuildContext context, Widget child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
primaryColor: primaryColor, //Head background
|
||||
accentColor: secondaryColor, //selection color
|
||||
dialogBackgroundColor: Colors.white, //Background color
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
var pickedDate = new DateTime(picked.year, picked.month, picked.day);
|
||||
var currentDate = new DateTime(
|
||||
DateTime.now().year, DateTime.now().month, DateTime.now().day);
|
||||
this._dateIndex = pickedDate == currentDate ? 0 : 1;
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
deliveryModel.filterData(
|
||||
status, _selectedDate, _selectedIndex, _dateIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var deliveryModel = Provider.of<DeliveryModel>(context);
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
var languageModle = Provider.of<LanguageModel>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(
|
||||
AppTranslations.of(context).text("delivery.title"),
|
||||
style: languageModle.isEng
|
||||
? TextStyle()
|
||||
: TextStyle(fontFamily: 'MyanmarUnicode'),
|
||||
),
|
||||
actions: <Widget>[
|
||||
InkWell(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
"assets/date_filter.png",
|
||||
color: Colors.white,
|
||||
width: 25,
|
||||
),
|
||||
_dateIndex == 0
|
||||
? Container()
|
||||
: Positioned(
|
||||
bottom: 15,
|
||||
right: 10,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () => _selectDate(context),
|
||||
),
|
||||
PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
_selectedIndex = selected.index;
|
||||
});
|
||||
if (selected.status == 'All') {
|
||||
status = null;
|
||||
} else {
|
||||
status = selected.status;
|
||||
}
|
||||
deliveryModel.filterData(
|
||||
status, _selectedDate, _selectedIndex, _dateIndex);
|
||||
},
|
||||
icon: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.filter_list,
|
||||
color: primaryColor,
|
||||
),
|
||||
_selectedIndex != 0
|
||||
? Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
)),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return deliveryStatusMenu.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
value: choice,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(choice.status),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
_selectedIndex != null && _selectedIndex == choice.index
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.grey,
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
],
|
||||
),
|
||||
body: LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: new ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: deliveryModel.dos.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Card(
|
||||
elevation: 10,
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DeliveryItem(
|
||||
doSubmission: deliveryModel.dos[index])),
|
||||
);
|
||||
},
|
||||
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/truck.png",
|
||||
width: 50,
|
||||
height: 50,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
deliveryModel.dos[index].doNumber,
|
||||
style: new TextStyle(
|
||||
fontSize: 12.0, color: Colors.black),
|
||||
),
|
||||
new Text(
|
||||
deliveryModel.dos[index].deliveryDate ==
|
||||
null
|
||||
? ""
|
||||
: dateFormatter.format(deliveryModel
|
||||
.dos[index].deliveryDate),
|
||||
style: new TextStyle(
|
||||
fontSize: 14.0, color: Colors.grey),
|
||||
),
|
||||
!isBuyer
|
||||
? new Text(
|
||||
deliveryModel.dos[index].userName,
|
||||
style: new TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.grey),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child:
|
||||
getStatus(deliveryModel.dos[index].status),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
194
lib/pages/device_list.dart
Normal file
194
lib/pages/device_list.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/device_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/vo/device.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/popupmenu.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class PhoneDeviceList extends StatefulWidget {
|
||||
@override
|
||||
_PhoneDeviceListState createState() => _PhoneDeviceListState();
|
||||
}
|
||||
|
||||
class _PhoneDeviceListState extends State<PhoneDeviceList> {
|
||||
final double dotSize = 15.0;
|
||||
PopupMenu selectedChoices = deviceMenu[0];
|
||||
bool _isLoading = false;
|
||||
PhoneDevice phoneDevice = new PhoneDevice();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var deviceModel = Provider.of<PhoneDeviceModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'profile.devices',
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
body: new ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: deviceModel.devices.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Card(
|
||||
elevation: 10,
|
||||
color: Colors.white,
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7.0),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: new EdgeInsets.symmetric(
|
||||
horizontal: 15.0 - dotSize / 2),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Image.asset(
|
||||
"assets/device.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
deviceModel.devices[index].name,
|
||||
style: new TextStyle(
|
||||
fontSize: 13.0, color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
onSelected: _select,
|
||||
itemBuilder: (BuildContext context) {
|
||||
this.phoneDevice = deviceModel.devices[index];
|
||||
return deviceMenu.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
enabled: choice.index == 0
|
||||
? deviceModel.devices[index].isDeviceOn()
|
||||
? false
|
||||
: true
|
||||
: choice.index == 1
|
||||
? deviceModel.devices[index]
|
||||
.isDeviceOn()
|
||||
? true
|
||||
: false
|
||||
: true,
|
||||
value: choice,
|
||||
child: Text(choice.status),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _select(PopupMenu choice) async {
|
||||
selectedChoices = choice;
|
||||
if (choice.index == 0) {
|
||||
showConfirmDialog(context, "device.confirm", () {
|
||||
_confirm();
|
||||
});
|
||||
} else if (choice.index == 1) {
|
||||
showConfirmDialog(context, "device.logout", () {
|
||||
_logout();
|
||||
});
|
||||
} else if (choice.index == 2) {
|
||||
showConfirmDialog(context, "device.set_primary", () {
|
||||
_setPrimaryDevice();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_confirm() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
var deviceModel = Provider.of<PhoneDeviceModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
|
||||
await deviceModel.confirmDevice(
|
||||
mainModel.user.phoneNumber, this.phoneDevice.id);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_logout() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
var deviceModel = Provider.of<PhoneDeviceModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
|
||||
await deviceModel.logoutDevice(
|
||||
mainModel.user.phoneNumber, this.phoneDevice.id);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_setPrimaryDevice() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
var deviceModel = Provider.of<PhoneDeviceModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
await deviceModel.setPrimaryDevice(
|
||||
mainModel.user.phoneNumber, this.phoneDevice.id);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
904
lib/pages/do/do_approve.dart
Normal file
904
lib/pages/do/do_approve.dart
Normal file
@@ -0,0 +1,904 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:quiver/async.dart';
|
||||
import 'package:fcs/model/do_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/log_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/po_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/pages/do/photo_page.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/img_file.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../document_log_page.dart';
|
||||
import '../util.dart';
|
||||
import 'do_files.dart';
|
||||
import 'do_storage_item.dart';
|
||||
|
||||
class DOApproval extends StatefulWidget {
|
||||
final DOSubmission doSubmission;
|
||||
const DOApproval({this.doSubmission});
|
||||
@override
|
||||
_DOApprovalState createState() => _DOApprovalState();
|
||||
}
|
||||
|
||||
class _DOApprovalState extends State<DOApproval> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
var doDateFormatter = new DateFormat('dd MMM yyyy - hh:mm a');
|
||||
|
||||
bool _isLoading = false;
|
||||
TextEditingController _date = new TextEditingController();
|
||||
TextEditingController _doDate = new TextEditingController();
|
||||
TextEditingController _number = new TextEditingController();
|
||||
TextEditingController _licence = new TextEditingController();
|
||||
TextEditingController _driver = new TextEditingController();
|
||||
TextEditingController _carNo = new TextEditingController();
|
||||
TextEditingController _type = new TextEditingController();
|
||||
TextEditingController _name = new TextEditingController();
|
||||
TextEditingController _bizName = new TextEditingController();
|
||||
TextEditingController _storage = new TextEditingController();
|
||||
TextEditingController _comment = new TextEditingController();
|
||||
DOSubmission doObj = DOSubmission();
|
||||
int _count;
|
||||
DateTime _result;
|
||||
DOFiles files = DOFiles();
|
||||
List<DOLine> doLines = new List();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
var mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
var doModel = Provider.of<DOModel>(context, listen: false);
|
||||
|
||||
doObj = widget.doSubmission;
|
||||
_date.text = doObj.deliveryDate != null
|
||||
? dateFormatter.format(doObj.deliveryDate)
|
||||
: "";
|
||||
_doDate.text =
|
||||
doObj.doDate != null ? doDateFormatter.format(doObj.doDate) : "";
|
||||
_number.text = doObj.doNumber.toString();
|
||||
_licence.text = doObj.driverLicenseNumber;
|
||||
_driver.text = doObj.driverName;
|
||||
_carNo.text = doObj.carNo;
|
||||
_type.text = doObj.type;
|
||||
_name.text = doObj.userName;
|
||||
_bizName.text = doObj.bizName;
|
||||
_storage.text = doObj.storageCharge == null
|
||||
? ""
|
||||
: numberFormatter.format(doObj.storageCharge);
|
||||
_comment.text = doObj.comment;
|
||||
|
||||
if (doObj.deliveryStatus == 'initiated') {
|
||||
_count = doModel.timber;
|
||||
|
||||
Duration diff = DateTime.now().difference(doObj.deliveryInitiatedTime);
|
||||
|
||||
if (diff.inMinutes < mainModel.setting.deliveryStartWaitMin) {
|
||||
var time = mainModel.setting.deliveryStartWaitMin - diff.inMinutes;
|
||||
new CountdownTimer(
|
||||
new Duration(minutes: time), new Duration(seconds: 1))
|
||||
.listen((data) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_count = data.remaining.inSeconds;
|
||||
doModel.addTimber(_count);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
_load();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
var logModel = Provider.of<LogModel>(context);
|
||||
String formattedTime;
|
||||
if (doObj.deliveryStatus == 'initiated') {
|
||||
_result = DateTime(
|
||||
doObj.deliveryInitiatedTime.year,
|
||||
doObj.deliveryInitiatedTime.month,
|
||||
doObj.deliveryInitiatedTime.day,
|
||||
doObj.deliveryInitiatedTime.hour,
|
||||
doObj.deliveryInitiatedTime.minute,
|
||||
_count);
|
||||
formattedTime = DateFormat.ms().format(_result);
|
||||
}
|
||||
|
||||
final doDateBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 15),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.do_date"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_doDate.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final dateBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 8),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.date"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_date.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final numberBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.do_num"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_number.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final driverBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.driver"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_driver.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final carNoBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.car"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_carNo.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final licenceBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.licence"),
|
||||
ImageFile(
|
||||
enabled: false,
|
||||
title: "Image",
|
||||
initialImgUrl: doObj.driverLicenceUrl,
|
||||
onFile: (file) {}),
|
||||
],
|
||||
),
|
||||
);
|
||||
final statusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.status"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doObj.status,
|
||||
style: doObj.isPending
|
||||
? textHighlightBlueStyle
|
||||
: doObj.isApproved
|
||||
? textHighlightGreenStyle
|
||||
: textHighlightRedStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final deliveryStatusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.delivery.status"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 15),
|
||||
child: Text(
|
||||
doObj.getDeliveryStatus,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
doObj.deliveryStatus == 'initiated'
|
||||
? Text(
|
||||
"(can start in $formattedTime)",
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final typeBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.type"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Text(
|
||||
_type.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final userNameBox = Container(
|
||||
padding: EdgeInsets.only(top: 5, left: 20),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.name"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Text(
|
||||
_name.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final bizNameBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.biz"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Text(
|
||||
_bizName.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final driverImgUrlBox = Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.driver.image"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: ImageFile(
|
||||
enabled: !isBuyer && doObj.deliveryStatus == null,
|
||||
initialImgUrl: doObj.driverImgUrl,
|
||||
title: "Image",
|
||||
imageSource: ImageSource.camera,
|
||||
onFile: (file) {
|
||||
doObj.driverImg = file;
|
||||
}),
|
||||
),
|
||||
]));
|
||||
|
||||
final receiptImagebox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 0),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.receipt"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: ImageFile(
|
||||
enabled: false,
|
||||
initialImgUrl: doObj.doReceiptUrl,
|
||||
title: "Receipt",
|
||||
),
|
||||
),
|
||||
]));
|
||||
|
||||
final deliveryInitTimeBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.delivery.init.time"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doObj.deliveryInitTime,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final storageBox = Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.storage_charge"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_storage.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final storagePaymentBox = Container(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.storage_receipt"),
|
||||
ImageFile(
|
||||
enabled: mainModel.user.isBuyer() ? true : false,
|
||||
title: "Receipt File",
|
||||
initialImgUrl: this.doObj.storageReceiptUrl,
|
||||
onFile: (file) {
|
||||
this.files.setStorageChargeFile = file;
|
||||
}),
|
||||
]));
|
||||
|
||||
final commentBox = Container(
|
||||
padding: EdgeInsets.only(top: 5, left: 20),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.comment"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_comment.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("do"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
|
||||
actions: <Widget>[
|
||||
mainModel.showHistoryBtn()
|
||||
? IconButton(
|
||||
icon: Icon(Icons.history),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
DocumentLogPage(docID: doObj.id)),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
isBuyer
|
||||
? doObj.isPending
|
||||
? PopupMenuButton(
|
||||
onSelected: _selectBuyer,
|
||||
itemBuilder: (context) => List<PopupMenuEntry>.from([
|
||||
PopupMenuItem(
|
||||
enabled: this.doObj.isPending &&
|
||||
this.doObj.storageCharge > 0,
|
||||
value: 1,
|
||||
child: Text("Update DO"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
enabled: this.doObj.isPending,
|
||||
value: 2,
|
||||
child: Text("Cancel DO"),
|
||||
),
|
||||
]))
|
||||
: Container()
|
||||
: PopupMenuButton(
|
||||
onSelected: _select,
|
||||
itemBuilder: (context) => List<PopupMenuEntry>.from([
|
||||
PopupMenuItem(
|
||||
enabled: this.doObj.isPending,
|
||||
value: 1,
|
||||
child: Text("Approve DO"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
enabled: this.doObj.isPending,
|
||||
value: 2,
|
||||
child: Text("Reject DO"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
enabled:
|
||||
this.doObj.isApproved && mainModel.user.isOwner(),
|
||||
value: 6,
|
||||
child: Text("Cancel DO"),
|
||||
)
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
padding:
|
||||
EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10),
|
||||
child: Card(
|
||||
elevation: 23,
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
doDateBox,
|
||||
Divider(),
|
||||
dateBox,
|
||||
Divider(),
|
||||
numberBox,
|
||||
Divider(),
|
||||
userNameBox,
|
||||
Divider(),
|
||||
bizNameBox,
|
||||
Divider(),
|
||||
typeBox,
|
||||
Divider(),
|
||||
statusBox,
|
||||
Divider(),
|
||||
doObj.comment == null || doObj.comment == ''
|
||||
? Container()
|
||||
: commentBox,
|
||||
doObj.comment == null || doObj.comment == ''
|
||||
? Container()
|
||||
: Divider(),
|
||||
driverBox,
|
||||
Divider(),
|
||||
carNoBox,
|
||||
Divider(),
|
||||
licenceBox,
|
||||
Divider(),
|
||||
receiptImagebox,
|
||||
Divider(),
|
||||
doObj.hasStorageCharge() ? storageBox : Container(),
|
||||
doObj.hasStorageCharge() ? Divider() : Container(),
|
||||
doObj.hasStorageCharge()
|
||||
? storagePaymentBox
|
||||
: Container(),
|
||||
doObj.isApproved || doObj.isClosed
|
||||
? deliveryStatusBox
|
||||
: Container(),
|
||||
doObj.isApproved || doObj.isClosed
|
||||
? Divider()
|
||||
: Container(),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: LocalText(
|
||||
context,
|
||||
'do.products',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
underline: true,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 40,
|
||||
columns: [
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.product"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.storage"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.quantity"),numeric: true
|
||||
),
|
||||
],
|
||||
rows: getProductRow(doObj.doLines),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
getPOProductTable()
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<DOLine> doLines) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
|
||||
if (doLines.isNotEmpty) {
|
||||
doLines.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doLines.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
}
|
||||
return doLines.map((d) {
|
||||
return MyDataRow(
|
||||
onSelectChanged: (bool selected) async {
|
||||
if (isBuyer) return;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DOStorageItem(
|
||||
doLine: d,
|
||||
onSave: (storageID, storageName) {
|
||||
setState(() {
|
||||
d.storageID = storageID;
|
||||
d.storageName = storageName;
|
||||
});
|
||||
},
|
||||
)),
|
||||
);
|
||||
},
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(d.storageName, style: textStyle),
|
||||
),
|
||||
MyDataCell(NumberCell(d.qty)),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Widget getPOProductTable() {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: LocalText(context, 'po.info',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
underline: true,
|
||||
color: secondaryColor),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "po.number")),
|
||||
MyDataColumn(label: LocalText(context, "po.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.po_qty"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.po_balance_qty"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.retrieved.amount"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.do_qty"),numeric: true,
|
||||
),
|
||||
],
|
||||
rows: getPOProductRow(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getPOProductRow() {
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
if (doObj.dopoLies.isNotEmpty) {
|
||||
doObj.dopoLies.sort((p1, p2) => p1.poNumber.compareTo(p2.poNumber));
|
||||
|
||||
doObj.dopoLies.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doObj.dopoLies.sort((p1, p2) {
|
||||
if (p1.displayOrder != p2.displayOrder)
|
||||
return p1.displayOrder.compareTo(p2.displayOrder);
|
||||
return p1.poNumber.compareTo(p2.poNumber);
|
||||
});
|
||||
}
|
||||
return doObj.dopoLies.map((d) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.poNumber,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(NumberCell(d.poQty)),
|
||||
MyDataCell(NumberCell(d.poBalAtCreate)),
|
||||
MyDataCell(NumberCell(d.getPoBalanceQtyAtCreate)),
|
||||
MyDataCell(NumberCell(d.doQty)),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_select(s) {
|
||||
if (s == 1) {
|
||||
showConfirmDialog(context, "do.approve.confirm", () {
|
||||
_approve();
|
||||
});
|
||||
} else if (s == 2) {
|
||||
showCommentDialog(context, (comment) {
|
||||
doObj.comment = comment;
|
||||
_reject();
|
||||
});
|
||||
} else if (s == 5) {
|
||||
showConfirmDialog(context, "do.end.confirm", () {
|
||||
_endDelivery();
|
||||
});
|
||||
} else if (s == 6) {
|
||||
showConfirmDialog(context, "do.cancel.confirm", () {
|
||||
_cancelDelivery();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_selectBuyer(s) {
|
||||
if (s == 1) {
|
||||
showConfirmDialog(context, "do.confirm", () {
|
||||
_submit();
|
||||
});
|
||||
} else if (s == 2) {
|
||||
showConfirmDialog(context, "do.cancel.confirm", () {
|
||||
_cancelDelivery();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
POSubmissionModel poModel =
|
||||
Provider.of<POSubmissionModel>(context, listen: false);
|
||||
DOModel doModel = Provider.of<DOModel>(context, listen: false);
|
||||
|
||||
var _doSub = await poModel.loadDOLines(doObj);
|
||||
_doSub = await doModel.loadDOPOLines(_doSub);
|
||||
// set po balance
|
||||
List<String> pos = _doSub.getPOs();
|
||||
for (var po in pos) {
|
||||
List<POLine> poLines = await poModel.loadPOLines(po);
|
||||
_doSub.setDOPOLineBalance(po, poLines);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
doObj.doLines = _doSub.doLines;
|
||||
doObj.dopoLies = _doSub.dopoLies;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_approve() async {
|
||||
if (doObj.doLines.any((l) => l.storageID.isEmpty)) {
|
||||
showMsgDialog(context, "Error", "Storage required for every product");
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.approveDO(doObj);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_reject() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.rejectDO(doObj);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_initDelivery() async {
|
||||
if (doObj.driverImg == null) {
|
||||
showMsgDialog(context, "Error", "Please attach driver image");
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.initDelivery(doObj);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_startDelivery() async {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
Duration diff = DateTime.now().difference(doObj.deliveryInitiatedTime);
|
||||
if (diff.inMinutes < mainModel.setting.deliveryStartWaitMin) {
|
||||
showMsgDialog(context, "Waiting...",
|
||||
"Can not start delivery, wait for ${mainModel.setting.deliveryStartWaitMin} minutes");
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.startDelivery(doObj);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_endDelivery() async {
|
||||
var photo = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => PhotoPage()),
|
||||
);
|
||||
if (photo == null) {
|
||||
return;
|
||||
}
|
||||
Uint8List bytesPhoto = photo.readAsBytesSync() as Uint8List;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.endDelivery(doObj, bytesPhoto);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_cancelDelivery() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.cancelDO(doObj);
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_submit() async {
|
||||
if (doObj.hasStorageCharge()) {
|
||||
if (files.storageChargeFile == null && doObj.storageReceiptUrl == '') {
|
||||
showMsgDialog(context, "Error", "Please insert storage receipt");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
await doModel.updateDO(doObj, files);
|
||||
Navigator.pop<bool>(context, true);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
650
lib/pages/do/do_creation_form.dart
Normal file
650
lib/pages/do/do_creation_form.dart
Normal file
@@ -0,0 +1,650 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/do_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/po_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/pages/do/do_product_item.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/img_file.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'do_files.dart';
|
||||
import 'po_selection.dart';
|
||||
|
||||
class DOForm extends StatefulWidget {
|
||||
final DOSubmission doSubmission;
|
||||
const DOForm({this.doSubmission});
|
||||
@override
|
||||
_DOFormState createState() => _DOFormState();
|
||||
}
|
||||
|
||||
class _DOFormState extends State<DOForm> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
TextEditingController _deliveryDate = new TextEditingController();
|
||||
TextEditingController _licence = new TextEditingController();
|
||||
TextEditingController _driver = new TextEditingController();
|
||||
TextEditingController _carNo = new TextEditingController();
|
||||
TextEditingController _doStatus = new TextEditingController();
|
||||
TextEditingController _storage = new TextEditingController();
|
||||
TextEditingController _doNumber = new TextEditingController();
|
||||
DOSubmission doSubmission = DOSubmission();
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isNew = true;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
int doTypeValue = 0;
|
||||
DOLine doLine = new DOLine();
|
||||
DOFiles files = DOFiles();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.doSubmission != null) {
|
||||
this.doSubmission = widget.doSubmission;
|
||||
this._isNew = false;
|
||||
_deliveryDate.text =
|
||||
dateFormatter.format(widget.doSubmission.deliveryDate);
|
||||
_licence.text = widget.doSubmission.driverLicenseNumber;
|
||||
_driver.text = widget.doSubmission.driverName;
|
||||
_carNo.text = widget.doSubmission.carNo;
|
||||
_doStatus.text = widget.doSubmission.status;
|
||||
_storage.text = widget.doSubmission.storageCharge == null
|
||||
? ""
|
||||
: numberFormatter.format(widget.doSubmission.storageCharge);
|
||||
_doNumber.text = widget.doSubmission.doNumber;
|
||||
|
||||
if (widget.doSubmission.type == 'multiple') {
|
||||
doTypeValue = 1;
|
||||
} else {
|
||||
doTypeValue = 0;
|
||||
}
|
||||
this.doLine.action = 'update';
|
||||
} else {
|
||||
this.doLine.action = 'create';
|
||||
doSubmission.type = 'single';
|
||||
_storage.text = "0";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
var poModel = Provider.of<POSubmissionModel>(context);
|
||||
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
|
||||
final doNumberBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 10),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.do_num"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_doNumber.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final _doTypeValueBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.type"),
|
||||
Container(
|
||||
width: 130,
|
||||
child: RadioListTile(
|
||||
dense: true,
|
||||
title: LocalText(context, "do.single"),
|
||||
value: 0,
|
||||
groupValue: doTypeValue,
|
||||
onChanged: handleRadioValueChanged,
|
||||
activeColor: primaryColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 136,
|
||||
child: RadioListTile(
|
||||
dense: true,
|
||||
title: LocalText(context, 'do.multiple'),
|
||||
value: 1,
|
||||
groupValue: doTypeValue,
|
||||
onChanged: handleRadioValueChanged,
|
||||
activeColor: primaryColor,
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final deliveryDateBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
DatePicker.showDatePicker(context,
|
||||
showTitleActions: true,
|
||||
currentTime: _deliveryDate.text == ""
|
||||
? null
|
||||
: dateFormatter.parse(_deliveryDate.text),
|
||||
minTime: DateTime.now(),
|
||||
maxTime: DateTime(2030, 12, 31), onConfirm: (date) {
|
||||
setState(() {
|
||||
_deliveryDate.text = dateFormatter.format(date);
|
||||
doSubmission.deliveryDate =
|
||||
dateFormatter.parse(_deliveryDate.text);
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
});
|
||||
}, locale: LocaleType.en);
|
||||
},
|
||||
child: TextFormField(
|
||||
controller: _deliveryDate,
|
||||
autofocus: false,
|
||||
cursorColor: primaryColor,
|
||||
style: textStyle,
|
||||
enabled: false,
|
||||
keyboardType: TextInputType.datetime,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
labelText: AppTranslations.of(context).text("do.date"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 0.0),
|
||||
icon: Icon(
|
||||
Icons.date_range,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.date");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
final driverBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: TextFormField(
|
||||
controller: _driver,
|
||||
autofocus: false,
|
||||
style: textStyle,
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text("do.driver"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
icon: Icon(
|
||||
Icons.account_box,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.driver");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
final carNoBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: TextFormField(
|
||||
controller: _carNo,
|
||||
autofocus: false,
|
||||
style: textStyle,
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text("do.car"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
icon: Icon(
|
||||
Icons.directions_car,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.car");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
final doStatusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: TextFormField(
|
||||
controller: _doStatus,
|
||||
autofocus: false,
|
||||
style: textStyle,
|
||||
readOnly: true,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
icon: Image.asset("assets/status.png",
|
||||
width: 25, color: primaryColor)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return "Please enter DO Status";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
final storageBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 10),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.storage_charge"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doSubmission.storageCharge == null
|
||||
? ""
|
||||
: numberFormatter.format(doSubmission.storageCharge),
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final storagePaymentBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.storage_receipt"),
|
||||
ImageFile(
|
||||
enabled: isBuyer,
|
||||
title: "Receipt File",
|
||||
initialImgUrl: this.doSubmission.storageReceiptUrl,
|
||||
onFile: (file) {
|
||||
this.files.setStorageChargeFile = file;
|
||||
}),
|
||||
]));
|
||||
final licesebox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.licence"),
|
||||
ImageFile(
|
||||
enabled: isBuyer,
|
||||
title: "Image",
|
||||
initialImgUrl: this.doSubmission.driverLicenceUrl,
|
||||
onFile: (file) {
|
||||
this.files.setlicenseFile = file;
|
||||
}),
|
||||
]));
|
||||
|
||||
final poButtun = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "po.title"),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
POSelection.showPOSelection(
|
||||
context, poModel.approvedPOs, doSubmission.pos,
|
||||
ok: (List<POSubmission> pos) async {
|
||||
for (var po in pos) {
|
||||
po.poLines = await poModel.loadPOLines(po.id);
|
||||
}
|
||||
setState(() {
|
||||
doSubmission.pos = pos;
|
||||
doSubmission.loadPOs();
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
});
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.edit)),
|
||||
]));
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("do"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.send),
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
showConfirmDialog(context, "do.confirm", () {
|
||||
_submit(mainModel);
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
this.doSubmission.doNumber != null
|
||||
? doNumberBox
|
||||
: Container(),
|
||||
this.doSubmission.hasStorageCharge()
|
||||
? storageBox
|
||||
: Container(),
|
||||
this.doSubmission.hasStorageCharge()
|
||||
? storagePaymentBox
|
||||
: Container(),
|
||||
_doTypeValueBox,
|
||||
deliveryDateBox,
|
||||
driverBox,
|
||||
licesebox,
|
||||
carNoBox,
|
||||
poButtun,
|
||||
widget.doSubmission == null ? Container() : doStatusBox,
|
||||
getProductTable(),
|
||||
getPOProductTable(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void handleRadioValueChanged(int value) {
|
||||
var mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
setState(() {
|
||||
doSubmission.type = value == 0 ? 'single' : 'multiple';
|
||||
doTypeValue = value;
|
||||
doSubmission.loadPOs();
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
switch (doTypeValue) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget getProductTable() {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: LocalText(
|
||||
context,
|
||||
'do.products',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
underline: true,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "do.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.avail.qty"),
|
||||
numeric: true
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.do_qty"),
|
||||
numeric: true,
|
||||
),
|
||||
],
|
||||
rows: getProductRow(doSubmission.doLines),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<DOLine> doLines) {
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
if (doLines.isNotEmpty) {
|
||||
doLines.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doLines.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
}
|
||||
return doLines.map((d) {
|
||||
return MyDataRow(
|
||||
onSelectChanged: (bool selected) async {
|
||||
if (doTypeValue == 0) return;
|
||||
var doLine = await showDialog(
|
||||
context: context,
|
||||
builder: (_) => DOProductItem(
|
||||
doLine: DOLine(productID: d.productID, qty: d.qty),
|
||||
));
|
||||
_updateQty(doLine);
|
||||
},
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.poBalQty)
|
||||
),
|
||||
MyDataCell(
|
||||
Container(
|
||||
color: Colors.cyan,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
new Text(d.qty == null ? "0" : d.qty.toString(),
|
||||
style: textStyle),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Widget getPOProductTable() {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: LocalText(context, 'po.info',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
underline: true,
|
||||
color: secondaryColor),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "po.number")),
|
||||
MyDataColumn(label: LocalText(context, "po.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.po_qty"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.avail.qty"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.retrieved.amount"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.do_qty"),numeric: true,
|
||||
),
|
||||
],
|
||||
rows: getPOProductRow(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getPOProductRow() {
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
if (doSubmission.dopoLies.isNotEmpty) {
|
||||
doSubmission.dopoLies
|
||||
.sort((p1, p2) => p1.poNumber.compareTo(p2.poNumber));
|
||||
doSubmission.dopoLies.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doSubmission.dopoLies.sort((p1, p2) {
|
||||
if (p1.displayOrder!=p2.displayOrder)
|
||||
return p1.displayOrder.compareTo(p2.displayOrder);
|
||||
return p1.poNumber.compareTo(p2.poNumber);
|
||||
});
|
||||
}
|
||||
return doSubmission.dopoLies.map((d) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.poNumber,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.poQty)
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.poBalQty)
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.getPoBalanceQty)
|
||||
),
|
||||
MyDataCell(
|
||||
Container(
|
||||
color: Colors.grey,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
new Text(d.doQty == null ? "0" : d.doQty.toString(),
|
||||
style: textStyle),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_updateQty(DOLine doLine) {
|
||||
if (doLine == null) return;
|
||||
|
||||
try {
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
setState(() {
|
||||
doSubmission.updateDoline(doLine.productID, doLine.qty);
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
});
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
_submit(MainModel mainModel) async {
|
||||
if (doSubmission.doLines.length == 0) {
|
||||
showMsgDialog(context, "Error", "No product line");
|
||||
return;
|
||||
}
|
||||
if (files.licenseFile == null) {
|
||||
showMsgDialog(context, "Error", "Please insert driver licence");
|
||||
return;
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
doSubmission.doLines.forEach((doLine) {
|
||||
total += doLine.qty;
|
||||
});
|
||||
|
||||
if (total <= 0) {
|
||||
showMsgDialog(context, "Error", "must be greater than zero");
|
||||
return;
|
||||
}
|
||||
|
||||
doSubmission.carNo = _carNo.text;
|
||||
doSubmission.driverName = _driver.text;
|
||||
doSubmission.driverLicenseNumber = _licence.text;
|
||||
doSubmission.deliveryDate = dateFormatter.parse(_deliveryDate.text);
|
||||
doSubmission.type = doTypeValue == 0 ? 'single' : 'multiple';
|
||||
doSubmission.doLines.removeWhere((d) => d.qty == 0);
|
||||
doSubmission.dopoLies.removeWhere((d) => d.doQty == 0);
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
|
||||
if (_isNew) {
|
||||
await doModel.createDO(doSubmission, files);
|
||||
} else {
|
||||
await doModel.updateDO(doSubmission, files);
|
||||
}
|
||||
Navigator.pop<bool>(context, true);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
423
lib/pages/do/do_creation_todelete.dart
Normal file
423
lib/pages/do/do_creation_todelete.dart
Normal file
@@ -0,0 +1,423 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/po_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/img_file.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:progress/progress.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'do_creation_form.dart';
|
||||
|
||||
class DoCreation extends StatefulWidget {
|
||||
final POSubmission poSubmission;
|
||||
const DoCreation({this.poSubmission});
|
||||
@override
|
||||
_DoCreationState createState() => _DoCreationState();
|
||||
}
|
||||
|
||||
class _DoCreationState extends State<DoCreation> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
bool _isLoading = false;
|
||||
TextEditingController _podate = new TextEditingController();
|
||||
TextEditingController _ponumber = new TextEditingController();
|
||||
TextEditingController _postatus = new TextEditingController();
|
||||
POSubmission poSubmission = POSubmission();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
this.poSubmission = widget.poSubmission;
|
||||
_podate.text = dateFormatter.format(this.poSubmission.poDate);
|
||||
_ponumber.text = this.poSubmission.poNumber.toString();
|
||||
_postatus.text = this.poSubmission.status;
|
||||
_isLoading = true;
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
List<POLine> poLines =
|
||||
await Provider.of<POSubmissionModel>(context, listen: false)
|
||||
.loadPOLines(poSubmission.id);
|
||||
var _poDos = await Provider.of<POSubmissionModel>(context, listen: false)
|
||||
.loadDOs(poSubmission);
|
||||
setState(() {
|
||||
this.poSubmission.poLines = poLines;
|
||||
this.poSubmission.dos = _poDos.dos;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget doDateBox(BuildContext context, DOSubmission doSubmission) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.date"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
dateFormatter.format(doSubmission.deliveryDate),
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget doNumberBox(BuildContext context, DOSubmission doSubmission) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.do_num"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doSubmission.doNumber,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget driverBox(BuildContext context, DOSubmission doSubmission) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.driver"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doSubmission.driverName,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget carNoBox(BuildContext context, DOSubmission doSubmission) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.car"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doSubmission.carNo,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget doStatusBox(BuildContext context, DOSubmission doSubmission) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.status"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doSubmission.status,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget driverLicence(BuildContext context, DOSubmission doSubmission) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.licence"),
|
||||
ImageFile(
|
||||
enabled: false,
|
||||
title: "Receipt File",
|
||||
initialImgUrl: doSubmission.driverLicenceUrl,
|
||||
onFile: (file) {}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModle = Provider.of<LanguageModel>(context);
|
||||
|
||||
final poDateBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 15),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "po.date"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_podate.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final poNumberBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.po_num"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_ponumber.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final poStatusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "po.status"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_postatus.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("do.title"),
|
||||
style: languageModle.isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: primaryColor,
|
||||
heroTag: "btn2",
|
||||
onPressed: () async {
|
||||
if (poSubmission.poLines
|
||||
.fold<int>(0, (p, e) => p + e.balanceQty) ==
|
||||
0) {
|
||||
showMsgDialog(context, "Error", "Zero PO balance qty");
|
||||
return;
|
||||
}
|
||||
|
||||
final bool successful = await Navigator.push<bool>(
|
||||
context, MaterialPageRoute(builder: (context) => DOForm()));
|
||||
if (successful != null && successful) _load();
|
||||
},
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10),
|
||||
child: Card(
|
||||
elevation: 23,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
poDateBox,
|
||||
poNumberBox,
|
||||
poStatusBox,
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 15,
|
||||
columns: [
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.product"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.po_qty"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label:
|
||||
LocalText(context, "do.po_balance_qty"),
|
||||
),
|
||||
],
|
||||
rows:
|
||||
getPoProductRow(widget.poSubmission.poLines),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: getDos(poSubmission.dos),
|
||||
),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
List<MyDataRow> getPoProductRow(List<POLine> poLines) {
|
||||
return poLines.map((p) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
p.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(p.qty.toString(), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(p.balanceQty.toString(), style: textStyle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<Widget> getDos(List<DOSubmission> dos) {
|
||||
return dos.map((d) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10),
|
||||
child: Card(
|
||||
child: Theme(
|
||||
data: ThemeData(accentColor: Colors.grey),
|
||||
child: ExpansionTile(
|
||||
onExpansionChanged: (e) => _onExpend(context, e, d),
|
||||
title: ListTile(
|
||||
title: Text(dateFormatter.format(d.deliveryDate)),
|
||||
subtitle: Text(d.doNumber),
|
||||
),
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 20),
|
||||
child: InkWell(
|
||||
child: Icon(Icons.edit),
|
||||
onTap: () async {
|
||||
final bool successful =
|
||||
await Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DOForm(
|
||||
doSubmission: d,
|
||||
)));
|
||||
if (successful != null && successful) _load();
|
||||
},
|
||||
)),
|
||||
),
|
||||
doDateBox(context, d),
|
||||
doNumberBox(context, d),
|
||||
doStatusBox(context, d),
|
||||
driverBox(context, d),
|
||||
carNoBox(context, d),
|
||||
driverLicence(context, d),
|
||||
Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 15,
|
||||
columns: [
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.product"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.do_qty"),
|
||||
),
|
||||
],
|
||||
rows: getdoProductRow(d.doLines),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<MyDataRow> getdoProductRow(List<DOLine> doLines) {
|
||||
return doLines.map((p) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
p.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(p.qty.toString(), style: textStyle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_onExpend(BuildContext context, expended, DOSubmission doSub) async {
|
||||
if (!expended) return;
|
||||
POSubmissionModel poModel = Provider.of<POSubmissionModel>(context);
|
||||
var _doSub = await poModel.loadDOLines(doSub);
|
||||
setState(() {
|
||||
doSub = _doSub;
|
||||
});
|
||||
}
|
||||
}
|
||||
26
lib/pages/do/do_files.dart
Normal file
26
lib/pages/do/do_files.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dart:io';
|
||||
|
||||
class DOFiles {
|
||||
File doPaymentFile, storageChargeFile, licenseFile;
|
||||
bool doFileChanged = false,
|
||||
storageFileChanged = false,
|
||||
licenseFileChanged = false;
|
||||
|
||||
set setDoPaymentFile(File file) {
|
||||
doPaymentFile = file;
|
||||
doFileChanged = true;
|
||||
}
|
||||
|
||||
set setStorageChargeFile(File file) {
|
||||
storageChargeFile = file;
|
||||
storageFileChanged = true;
|
||||
}
|
||||
|
||||
set setlicenseFile(File file) {
|
||||
licenseFile = file;
|
||||
licenseFileChanged = true;
|
||||
}
|
||||
|
||||
bool get anyChanged =>
|
||||
doFileChanged || storageFileChanged || licenseFileChanged;
|
||||
}
|
||||
286
lib/pages/do/do_list.dart
Normal file
286
lib/pages/do/do_list.dart
Normal file
@@ -0,0 +1,286 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/do_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/pages/do/do_creation_form.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/popup_menu.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/popupmenu.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'do_approve.dart';
|
||||
|
||||
class DOList extends StatefulWidget {
|
||||
@override
|
||||
_DOListState createState() => _DOListState();
|
||||
}
|
||||
|
||||
class _DOListState extends State<DOList> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
final double dotSize = 10.0;
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
String status;
|
||||
int _selectedIndex = 0;
|
||||
int _dateIndex = 0;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
var doModel = Provider.of<DOModel>(context, listen: false);
|
||||
_selectedIndex = doModel.popupMenu.index;
|
||||
_dateIndex = doModel.dateIndex;
|
||||
_selectedDate = doModel.selectedDate;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<Null> _selectDate(BuildContext context) async {
|
||||
var doModel = Provider.of<DOModel>(context);
|
||||
|
||||
final DateTime picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(2101),
|
||||
builder: (BuildContext context, Widget child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
primaryColor: primaryColor, //Head background
|
||||
accentColor: secondaryColor, //selection color
|
||||
dialogBackgroundColor: Colors.white, //Background color
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
var pickedDate = new DateTime(picked.year, picked.month, picked.day);
|
||||
var currentDate = new DateTime(
|
||||
DateTime.now().year, DateTime.now().month, DateTime.now().day);
|
||||
this._dateIndex = pickedDate == currentDate ? 0 : 1;
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
doModel.filterData(status, _selectedDate, _selectedIndex, _dateIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var doModel = Provider.of<DOModel>(context);
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
var languageModle = Provider.of<LanguageModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(
|
||||
AppTranslations.of(context).text("do.title"),
|
||||
style: languageModle.isEng
|
||||
? TextStyle()
|
||||
: TextStyle(fontFamily: 'MyanmarUnicode'),
|
||||
),
|
||||
actions: <Widget>[
|
||||
InkWell(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
"assets/date_filter.png",
|
||||
color: Colors.white,
|
||||
width: 25,
|
||||
),
|
||||
_dateIndex == 0
|
||||
? Container()
|
||||
: Positioned(
|
||||
bottom: 15,
|
||||
right: 10,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () => _selectDate(context),
|
||||
),
|
||||
PopupMenuButton<PopupMenu>(
|
||||
elevation: 3.2,
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
_selectedIndex = selected.index;
|
||||
});
|
||||
if (selected.status == 'All') {
|
||||
status = null;
|
||||
} else {
|
||||
status = selected.status;
|
||||
}
|
||||
doModel.filterData(
|
||||
status, _selectedDate, _selectedIndex, _dateIndex);
|
||||
},
|
||||
icon: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.filter_list,
|
||||
color: primaryColor,
|
||||
),
|
||||
_selectedIndex != 0
|
||||
? Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
)),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return statusMenu.map((PopupMenu choice) {
|
||||
return PopupMenuItem<PopupMenu>(
|
||||
value: choice,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(choice.status),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
_selectedIndex != null &&
|
||||
_selectedIndex == choice.index
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.grey,
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}),
|
||||
],
|
||||
),
|
||||
floatingActionButton: mainModel.isBuyer()
|
||||
? FloatingActionButton(
|
||||
backgroundColor: primaryColor,
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DOForm()),
|
||||
),
|
||||
child: Icon(Icons.add),
|
||||
)
|
||||
: null,
|
||||
body: new ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: doModel.dos.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Card(
|
||||
elevation: 10,
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DOApproval(
|
||||
doSubmission: doModel.dos[index])),
|
||||
);
|
||||
},
|
||||
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: Padding(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Image.asset(
|
||||
"assets/do.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
doModel.dos[index].doNumber,
|
||||
style: new TextStyle(
|
||||
fontSize: 12.0, color: Colors.black),
|
||||
),
|
||||
new Text(
|
||||
doModel.dos[index].deliveryDate == null
|
||||
? ""
|
||||
: dateFormatter.format(
|
||||
doModel.dos[index].deliveryDate),
|
||||
style: new TextStyle(
|
||||
fontSize: 14.0, color: Colors.grey),
|
||||
),
|
||||
!isBuyer
|
||||
? new Text(
|
||||
doModel.dos[index].userName,
|
||||
style: new TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.grey),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
child: getStatus(doModel.dos[index].status),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
134
lib/pages/do/do_product_item.dart
Normal file
134
lib/pages/do/do_product_item.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class DOProductItem extends StatefulWidget {
|
||||
final DOLine doLine;
|
||||
const DOProductItem({Key key, this.doLine}) : super(key: key);
|
||||
@override
|
||||
_DOProductItemState createState() => _DOProductItemState();
|
||||
}
|
||||
|
||||
class _DOProductItemState extends State<DOProductItem> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
TextEditingController _doQty = new TextEditingController();
|
||||
DOLine doLine;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.doLine != null) {
|
||||
doLine = widget.doLine;
|
||||
_doQty.text = widget.doLine.qty.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: AlertDialog(
|
||||
title: Center(
|
||||
child: Text(
|
||||
AppTranslations.of(context).text("do_qty"),
|
||||
style: TextStyle(
|
||||
color: primaryColor, fontWeight: FontWeight.bold, fontSize: 20),
|
||||
)),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Expanded(
|
||||
child: new TextFormField(
|
||||
keyboardType: TextInputType.number,
|
||||
autofocus: true,
|
||||
controller: _doQty,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
fillColor: primaryColor,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.sortNumericUpAlt,
|
||||
color: primaryColor,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.volume");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: LocalText(
|
||||
context,
|
||||
'do.cancel',
|
||||
color: secondaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
_doQty.clear();
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
FlatButton(
|
||||
color: primaryColor,
|
||||
child: LocalText(
|
||||
context,
|
||||
'do.enter',
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
onPressed: () async {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
_save();
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_save() {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
var qty = int.parse(_doQty.text);
|
||||
if (qty < 0)
|
||||
throw Exception("invalid number, must be zero or greater than zero");
|
||||
// if (qty > doLine.poLine.balanceQty)
|
||||
// throw Exception(
|
||||
// "invalid number, must be less than or equal to PO balance qty");
|
||||
this.doLine.qty = qty;
|
||||
Navigator.pop<DOLine>(context, this.doLine);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_delete() {
|
||||
this.doLine.action = "delete";
|
||||
this.doLine.qty = 0;
|
||||
Navigator.pop<DOLine>(context, this.doLine);
|
||||
}
|
||||
}
|
||||
209
lib/pages/do/do_storage_item.dart
Normal file
209
lib/pages/do/do_storage_item.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/storage_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/util.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/storage.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
typedef OnSave = void Function(String storageID, String storageName);
|
||||
|
||||
class DOStorageItem extends StatefulWidget {
|
||||
final DOLine doLine;
|
||||
final OnSave onSave;
|
||||
const DOStorageItem({Key key, this.doLine, this.onSave}) : super(key: key);
|
||||
@override
|
||||
_DOStorageItemState createState() => _DOStorageItemState();
|
||||
}
|
||||
|
||||
class _DOStorageItemState extends State<DOStorageItem> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
String currentStorageID;
|
||||
TextEditingController _product = new TextEditingController();
|
||||
TextEditingController _quantity = new TextEditingController();
|
||||
DOLine doLine = DOLine();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
this.doLine = widget.doLine;
|
||||
|
||||
if (doLine.storageID != null && doLine.storageID.isNotEmpty) {
|
||||
this.currentStorageID = doLine.storageID;
|
||||
}
|
||||
_product.text = this.doLine.productName;
|
||||
_quantity.text = this.doLine.qty.toString();
|
||||
}
|
||||
|
||||
Widget showStorages(BuildContext context, StorageModel storageModel) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
"assets/inventory.png",
|
||||
width: 30,
|
||||
height: 30,
|
||||
color: primaryColor,
|
||||
),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
new Flexible(
|
||||
child: Container(
|
||||
width: 170.0,
|
||||
child: DropdownButton<String>(
|
||||
value: currentStorageID,
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'Select Storage',
|
||||
style: labelStyle,
|
||||
),
|
||||
onChanged: changedStorage,
|
||||
items: storageModel
|
||||
.getStorage(doLine.productID)
|
||||
.map<DropdownMenuItem<String>>((Storage storage) {
|
||||
return new DropdownMenuItem<String>(
|
||||
value: storage.id,
|
||||
child: new Text(storage.name, style: textStyle),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void changedStorage(selected) {
|
||||
setState(() {
|
||||
currentStorageID = selected;
|
||||
});
|
||||
}
|
||||
|
||||
Widget showStorgeTable(BuildContext context, StorageModel storageModel) {
|
||||
return Container(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 100,
|
||||
columns: [
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "storge"),
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "storage.product.qty"),
|
||||
),
|
||||
],
|
||||
rows: getProductRow(storageModel.getStorage(doLine.productID)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<Storage> storages) {
|
||||
return storages.map((s) {
|
||||
return MyDataRow(
|
||||
onSelectChanged: (bool selected) {
|
||||
setState(() {
|
||||
currentStorageID = s.id;
|
||||
});
|
||||
},
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(s.name, style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(s.products
|
||||
.firstWhere((p) => p.id == doLine.productID)
|
||||
.quantity),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var storgeModel = Provider.of<StorageModel>(context);
|
||||
|
||||
final productbox = Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.product"),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Text(_product.text, style: textStyle)
|
||||
],
|
||||
));
|
||||
|
||||
final quantitybox = Container(
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.quantity"),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Text(formatNumber(this.doLine.qty), style: textStyle)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text("DO"),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.save),
|
||||
onPressed: () {
|
||||
_save();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0, top: 10),
|
||||
children: <Widget>[
|
||||
productbox,
|
||||
quantitybox,
|
||||
showStorages(context, storgeModel),
|
||||
showStorgeTable(context, storgeModel)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
_save() {
|
||||
this.doLine.storageID = currentStorageID;
|
||||
var storageName =
|
||||
Provider.of<StorageModel>(context).getStorageName(currentStorageID);
|
||||
this.doLine.storageName = storageName;
|
||||
if (widget.onSave != null)
|
||||
widget.onSave(this.doLine.storageID, storageName);
|
||||
Navigator.pop<DOLine>(context, this.doLine);
|
||||
}
|
||||
}
|
||||
165
lib/pages/do/photo_page.dart
Normal file
165
lib/pages/do/photo_page.dart
Normal file
@@ -0,0 +1,165 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
class PhotoPage extends StatefulWidget {
|
||||
PhotoPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_PhotoPageState createState() => _PhotoPageState();
|
||||
}
|
||||
|
||||
class _PhotoPageState extends State<PhotoPage> {
|
||||
File receiptImageFile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
|
||||
final receiptImageBox = Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: Container(
|
||||
color: Colors.grey[400],
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
child: Text(
|
||||
AppTranslations.of(context).text('do.receipt'),
|
||||
style:
|
||||
languageModel.isEng ? photoLabelStyle : photoLabelStyleMM,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ButtonTheme.bar(
|
||||
child: new ButtonBar(
|
||||
alignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new FlatButton(
|
||||
child: Icon(
|
||||
Icons.add_photo_alternate,
|
||||
size: 60.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
onPressed: () {
|
||||
pickImageFromGallery(ImageSource.gallery);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
ButtonTheme.bar(
|
||||
child: new ButtonBar(
|
||||
alignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new FlatButton(
|
||||
child: Icon(
|
||||
Icons.add_a_photo,
|
||||
size: 50.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
onPressed: () {
|
||||
pickImageFromCamera(ImageSource.camera);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
receiptShowImage(),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("do.receipt.title"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.send),
|
||||
onPressed: () {
|
||||
if (receiptImageFile == null) {
|
||||
showMsgDialog(
|
||||
context, "Error", "Please insert delivery receipt file");
|
||||
return;
|
||||
}
|
||||
Navigator.pop(context, receiptImageFile);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0, top: 20),
|
||||
children: <Widget>[
|
||||
receiptImageBox,
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget receiptShowImage() {
|
||||
return Container(
|
||||
child: receiptImageFile == null ? initialImage() : receiptEnableImage(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget initialImage() {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
|
||||
return Center(
|
||||
child: Text(
|
||||
AppTranslations.of(context).text('do.no.photo'),
|
||||
style: languageModel.isEng ? photoLabelStyle : photoLabelStyleMM,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget receiptEnableImage() {
|
||||
return Center(
|
||||
child: Image.file(
|
||||
receiptImageFile,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pickImageFromGallery(ImageSource source) async {
|
||||
var tempImage = await ImagePicker.pickImage(
|
||||
source: source, imageQuality: 80, maxWidth: 300);
|
||||
setState(() {
|
||||
receiptImageFile = tempImage;
|
||||
});
|
||||
}
|
||||
|
||||
pickImageFromCamera(ImageSource source) async {
|
||||
var tempImage = await ImagePicker.pickImage(
|
||||
source: source, imageQuality: 80, maxWidth: 300);
|
||||
setState(() {
|
||||
receiptImageFile = tempImage;
|
||||
});
|
||||
}
|
||||
}
|
||||
158
lib/pages/do/po_selection.dart
Normal file
158
lib/pages/do/po_selection.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
|
||||
class POSelection extends StatefulWidget {
|
||||
final List<POSubmission> pos;
|
||||
final List<POSubmission> selectedPOs;
|
||||
|
||||
POSelection({Key key, this.pos, this.selectedPOs}) : super(key: key);
|
||||
|
||||
@override
|
||||
_POSelectionState createState() => new _POSelectionState();
|
||||
|
||||
static Future<void> showPOSelection(BuildContext context,
|
||||
List<POSubmission> pos, List<POSubmission> selectedPOs,
|
||||
{ok(List<POSubmission> pos)}) async {
|
||||
List<POSubmission> _selectedPOs = [];
|
||||
selectedPOs.forEach((i) => _selectedPOs.add(i));
|
||||
|
||||
final poselection = POSelection(
|
||||
pos: pos,
|
||||
selectedPOs: _selectedPOs,
|
||||
);
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
title: Center(
|
||||
child: LocalText(context, "po.title"),
|
||||
),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[
|
||||
poselection,
|
||||
Container(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
child: LocalText(context, "Cancel"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
FlatButton(
|
||||
color: primaryColor,
|
||||
child: LocalText(context, "Ok"),
|
||||
onPressed: () async {
|
||||
if (ok != null) ok(_selectedPOs);
|
||||
Navigator.of(context).pop();
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _POSelectionState extends State<POSelection> {
|
||||
List<POSubmission> pos;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
pos = widget.pos;
|
||||
pos.sort((p1, p2) => p1.poNumber.compareTo(p2.poNumber));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var width = MediaQuery.of(context).size.width * 0.8;
|
||||
var height = MediaQuery.of(context).size.height * 0.5;
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Select All"),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
widget.selectedPOs.clear();
|
||||
widget.selectedPOs.addAll(pos);
|
||||
});
|
||||
}),
|
||||
Container(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Column(
|
||||
children: pos.asMap().entries.map((p) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (widget.selectedPOs.contains(p.value)) {
|
||||
widget.selectedPOs.remove(p.value);
|
||||
} else {
|
||||
widget.selectedPOs.add(p.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
onChanged: (v) => {_update(p.key)},
|
||||
value: widget.selectedPOs.contains(p.value),
|
||||
),
|
||||
Text(p.value.poNumber),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
// child: ListView.builder(
|
||||
// itemCount: pos.length,
|
||||
// scrollDirection: Axis.vertical,
|
||||
// itemBuilder: (BuildContext ctxt, int index) {
|
||||
// return InkWell(
|
||||
// onTap: () {
|
||||
// setState(() {
|
||||
// if (widget.selectedPOs.contains(pos[index])) {
|
||||
// widget.selectedPOs.remove(pos[index]);
|
||||
// } else {
|
||||
// widget.selectedPOs.add(pos[index]);
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
// child: Row(
|
||||
// children: <Widget>[
|
||||
// Checkbox(
|
||||
// onChanged: (v) => {_update(index)},
|
||||
// value: widget.selectedPOs.contains(pos[index]),
|
||||
// ),
|
||||
// Text(pos[index].poNumber),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_update(int index) {
|
||||
setState(() {
|
||||
if (widget.selectedPOs.contains(pos[index])) {
|
||||
widget.selectedPOs.remove(pos[index]);
|
||||
} else {
|
||||
widget.selectedPOs.add(pos[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
87
lib/pages/document_log_page.dart
Normal file
87
lib/pages/document_log_page.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/log_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/document_log.dart';
|
||||
import 'package:fcs/vo/role.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
|
||||
class DocumentLogPage extends StatefulWidget {
|
||||
final String docID;
|
||||
|
||||
const DocumentLogPage({Key key, this.docID}) : super(key: key);
|
||||
@override
|
||||
_DocumentLogPageState createState() => _DocumentLogPageState();
|
||||
}
|
||||
|
||||
class _DocumentLogPageState extends State<DocumentLogPage> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy\nhh:mm:ss a');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.docID != null) {
|
||||
Provider.of<LogModel>(context, listen: false).loadDocLogs(widget.docID);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var logModel = Provider.of<LogModel>(context);
|
||||
var userModel = Provider.of<UserModel>(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: LocalText(context, 'document.log.title',
|
||||
fontSize: 20, color: Colors.white),
|
||||
backgroundColor: primaryColor,
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 40,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "document.date")),
|
||||
MyDataColumn(label: LocalText(context, "document.by")),
|
||||
MyDataColumn(label: LocalText(context, "document.desc")),
|
||||
],
|
||||
rows: getProductRow(logModel.docLogs, userModel.privileges),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(
|
||||
List<DocLog> docLogs, List<Privilege> privileges) {
|
||||
return docLogs.map((d) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(dateFormatter.format(d.date), style: textStyle),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.actionerName == null ? '' : d.actionerName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(d.getDesc(privileges) == null ? '' : d.getDesc(privileges),
|
||||
style: textStyle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
158
lib/pages/email_page.dart
Normal file
158
lib/pages/email_page.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/shared_pref.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'confirm_email.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class EmailPage extends StatefulWidget {
|
||||
final User user;
|
||||
EmailPage({this.user});
|
||||
@override
|
||||
_EmailPageState createState() => _EmailPageState();
|
||||
}
|
||||
|
||||
class _EmailPageState extends State<EmailPage> {
|
||||
final TextEditingController _email = new TextEditingController();
|
||||
bool _isLoading = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.user != null) {
|
||||
_email.text = widget.user.email;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
final emailInput = TextFormField(
|
||||
controller: _email,
|
||||
autofocus: false,
|
||||
decoration: new InputDecoration(
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
labelText: AppTranslations.of(context).text("login.email"),
|
||||
labelStyle: labelStyle,
|
||||
icon: Icon(
|
||||
Icons.email,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("login.email.empty");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final enterButton = Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: RaisedButton(
|
||||
onPressed: () => _add(mainModel),
|
||||
padding: EdgeInsets.all(10),
|
||||
color: primaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
),
|
||||
child: Text(AppTranslations.of(context).text("login.email.add"),
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
);
|
||||
|
||||
final skipButton = Container(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
SharedPref.saveSkippedRecoverEmail(true);
|
||||
Navigator.pushNamedAndRemoveUntil(context, "/home", (r) => false);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
AppTranslations.of(context).text("login.email.skip"),
|
||||
style: languageModel.isEng
|
||||
? TextStyle(fontSize: 18, fontFamily: 'WorkSansMedium')
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode'),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 4),
|
||||
child: Icon(
|
||||
Icons.skip_next,
|
||||
size: 30,
|
||||
)),
|
||||
],
|
||||
)),
|
||||
);
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: LocalText(
|
||||
context,
|
||||
'login.email.title',
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
backgroundColor: primaryColor,
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
children: <Widget>[
|
||||
Form(key: _formKey, child: emailInput),
|
||||
SizedBox(height: 8.0),
|
||||
enterButton,
|
||||
widget.user != null ? Container() : skipButton
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _add(MainModel mainModel) async {
|
||||
if (!_formKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
await userModel.addEmail(mainModel.user.phoneNumber, _email.text);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ConfirmEmail(
|
||||
id: mainModel.user.phoneNumber,
|
||||
email: _email.text,
|
||||
)));
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
370
lib/pages/employee_editor.dart
Normal file
370
lib/pages/employee_editor.dart
Normal file
@@ -0,0 +1,370 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/employee_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/log_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/role.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
import 'document_log_page.dart';
|
||||
import 'util.dart';
|
||||
|
||||
typedef void FindCallBack();
|
||||
|
||||
class EmployeeEditor extends StatefulWidget {
|
||||
final User employee;
|
||||
const EmployeeEditor({this.employee});
|
||||
@override
|
||||
_EmployeeEditorState createState() => _EmployeeEditorState();
|
||||
}
|
||||
|
||||
class _EmployeeEditorState extends State<EmployeeEditor> {
|
||||
TextEditingController _name = new TextEditingController();
|
||||
TextEditingController _phone = new TextEditingController();
|
||||
TextEditingController _phoneInput = new TextEditingController();
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
String currentBizId;
|
||||
bool isSend = false;
|
||||
User user;
|
||||
User selectedUser;
|
||||
List<Privilege> privileges = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
privileges = Provider.of<UserModel>(context, listen: false).getPrivileges;
|
||||
if (widget.employee != null) {
|
||||
_name.text = widget.employee.name;
|
||||
_phone.text = widget.employee.phone;
|
||||
privileges.forEach((p) => widget.employee.privilegeIds.contains(p.id)
|
||||
? p.isChecked = true
|
||||
: p.isChecked = false);
|
||||
} else {
|
||||
privileges.forEach((p) => p.isChecked = false);
|
||||
}
|
||||
}
|
||||
|
||||
Widget showprivilegeList(BuildContext context, UserModel userModel) {
|
||||
return Container(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: ListView.builder(
|
||||
itemCount: privileges.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return new ListTile(
|
||||
title: new Row(
|
||||
children: <Widget>[
|
||||
new Checkbox(
|
||||
value: privileges[index].isChecked == null
|
||||
? false
|
||||
: privileges[index].isChecked,
|
||||
activeColor: primaryColor,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
privileges[index].isChecked = value;
|
||||
});
|
||||
}),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
userModel.getPrivileges[index].name,
|
||||
style: TextStyle(
|
||||
fontSize: 15.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
child: new Text(
|
||||
userModel.getPrivileges[index].desc,
|
||||
style:
|
||||
TextStyle(fontSize: 12.0, color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget phoneInputbox(BuildContext context, FindCallBack findCallBack) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Stack(
|
||||
alignment: const Alignment(1.2, 1.0),
|
||||
children: <Widget>[
|
||||
TextFormField(
|
||||
controller: _phoneInput,
|
||||
autofocus: false,
|
||||
cursorColor: primaryColor,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: textStyle,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text('employee.phone'),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
icon: Icon(
|
||||
Icons.phone,
|
||||
color: primaryColor,
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryColor, width: 1.0)),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return "Please enter phone number";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
new FlatButton(
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
this.isSend = true;
|
||||
findCallBack();
|
||||
},
|
||||
child: new Icon(
|
||||
Icons.search,
|
||||
size: 25,
|
||||
))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var userModel = Provider.of<UserModel>(context);
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
|
||||
final namebox = TextFormField(
|
||||
controller: _name,
|
||||
autofocus: false,
|
||||
readOnly: true,
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
icon: Icon(
|
||||
Icons.person,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final displayPhoneNo = TextFormField(
|
||||
controller: _phone,
|
||||
autofocus: false,
|
||||
readOnly: true,
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
icon: Icon(
|
||||
Icons.phone,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
var phoneNumberBox = Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: displayPhoneNo),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => call(context, _phone.text),
|
||||
child: Icon(
|
||||
Icons.open_in_new,
|
||||
color: Colors.grey,
|
||||
size: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final saveButton = Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: 24.0,
|
||||
right: 24.0,
|
||||
),
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
color: primaryColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: 900.0,
|
||||
height: 100.0,
|
||||
child: FlatButton(
|
||||
onPressed: _save,
|
||||
child: LocalText(
|
||||
context,
|
||||
'employee.save',
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final addButton = Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: 24.0,
|
||||
right: 24.0,
|
||||
),
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
color: primaryColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: 900.0,
|
||||
height: 100.0,
|
||||
child: FlatButton(
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
_add(context);
|
||||
},
|
||||
child: LocalText(
|
||||
context,
|
||||
'employee.add',
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
"employee.item.title",
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
actions: <Widget>[
|
||||
widget.employee == null || !mainModel.showHistoryBtn()
|
||||
? Container()
|
||||
: IconButton(
|
||||
icon: Icon(Icons.history),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DocumentLogPage(
|
||||
docID: widget.employee.docID)),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
children: <Widget>[
|
||||
widget.employee == null
|
||||
? phoneInputbox(context, () => _findUser(context))
|
||||
: phoneNumberBox,
|
||||
widget.employee == null
|
||||
? this.isSend ? namebox : Container()
|
||||
: namebox,
|
||||
showprivilegeList(context, userModel)
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.employee == null ? addButton : saveButton,
|
||||
SizedBox(
|
||||
height: 20,
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
_add(BuildContext context) async {
|
||||
if (selectedUser == null) return;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
var employeeModel = Provider.of<EmployeeModel>(context);
|
||||
try {
|
||||
await employeeModel.updatePrivileges(
|
||||
this.selectedUser.docID, privilegesIDs());
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<String> privilegesIDs() {
|
||||
return this.privileges.where((p) => p.isChecked).map((p) => p.id).toList();
|
||||
}
|
||||
|
||||
_save() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
if (widget.employee == null) return;
|
||||
var employeeModel = Provider.of<EmployeeModel>(context);
|
||||
try {
|
||||
await employeeModel.updatePrivileges(
|
||||
widget.employee.docID, privilegesIDs());
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_findUser(BuildContext context) async {
|
||||
var userModel = Provider.of<UserModel>(context);
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
selectedUser = await userModel.findUser(_phoneInput.text);
|
||||
setState(() {
|
||||
isSend = true;
|
||||
_name.text = selectedUser.name;
|
||||
if (selectedUser.privilegeIds != null) {
|
||||
privileges.forEach((p) => selectedUser.privilegeIds.contains(p.id)
|
||||
? p.isChecked = true
|
||||
: p.isChecked = false);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
128
lib/pages/employee_list.dart
Normal file
128
lib/pages/employee_list.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:progress/progress.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/employee_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'employee_editor.dart';
|
||||
|
||||
class EmployeeList extends StatefulWidget {
|
||||
@override
|
||||
_EmployeeListState createState() => _EmployeeListState();
|
||||
}
|
||||
|
||||
class _EmployeeListState extends State<EmployeeList> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy - hh:mm:ss a');
|
||||
final double dotSize = 15.0;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var employeeModel = Provider.of<EmployeeModel>(context);
|
||||
var languageModle = Provider.of<LanguageModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(
|
||||
AppTranslations.of(context).text("employee.title"),
|
||||
style: languageModle.isEng
|
||||
? TextStyle()
|
||||
: TextStyle(fontFamily: 'MyanmarUnicode'),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: primaryColor,
|
||||
child: Icon(Icons.add),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => EmployeeEditor()),
|
||||
);
|
||||
},
|
||||
),
|
||||
body: new ListView.builder(
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: employeeModel.employees.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EmployeeEditor(
|
||||
employee: employeeModel.employees[index],
|
||||
)),
|
||||
);
|
||||
},
|
||||
child: Card(
|
||||
elevation: 10,
|
||||
color: Colors.white,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: new EdgeInsets.symmetric(
|
||||
horizontal: 32.0 - dotSize / 2),
|
||||
child: Image.asset(
|
||||
"assets/employee.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
employeeModel.employees[index].name == null
|
||||
? ""
|
||||
: employeeModel.employees[index].name,
|
||||
style: new TextStyle(
|
||||
fontSize: 17.0, color: Colors.black),
|
||||
),
|
||||
Text(
|
||||
employeeModel.employees[index].phone ==
|
||||
null
|
||||
? ""
|
||||
: employeeModel
|
||||
.employees[index].phone,
|
||||
style: new TextStyle(
|
||||
fontSize: 14.0, color: Colors.grey),
|
||||
),
|
||||
employeeModel.employees[index].device == null
|
||||
? Text("No login",
|
||||
style: TextStyle(color: Colors.red))
|
||||
: Text("last active",
|
||||
style: TextStyle(color: Colors.green)),
|
||||
Text(
|
||||
"${employeeModel.employees[index].device == null ? "" : employeeModel.employees[index].device}",
|
||||
style: TextStyle(fontSize: 11)),
|
||||
Text(
|
||||
"${employeeModel.employees[index].lastActiveTime == null ? "" : dateFormatter.format(employeeModel.employees[index].lastActiveTime)}")
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
126
lib/pages/forget_password.dart
Normal file
126
lib/pages/forget_password.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:progress/progress.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/pages/reset_password.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
class ForgetPassword extends StatefulWidget {
|
||||
final phoneNumber;
|
||||
|
||||
const ForgetPassword({Key key, this.phoneNumber}) : super(key: key);
|
||||
@override
|
||||
_ForgetPasswordState createState() => _ForgetPasswordState();
|
||||
}
|
||||
|
||||
class _ForgetPasswordState extends State<ForgetPassword> {
|
||||
final TextEditingController _email = new TextEditingController();
|
||||
bool _isLoading = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_email.text = widget.phoneNumber;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final emailInput = TextFormField(
|
||||
controller: _email,
|
||||
autofocus: false,
|
||||
decoration: new InputDecoration(
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
labelText: AppTranslations.of(context).text("forget.email"),
|
||||
labelStyle: labelStyle,
|
||||
icon: Icon(
|
||||
Icons.email,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("forget.email.empty");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final enterButton = Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: RaisedButton(
|
||||
onPressed: () => _forgetPassword(),
|
||||
padding: EdgeInsets.all(10),
|
||||
color: primaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
),
|
||||
child: Text(AppTranslations.of(context).text("forget.enter"),
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
);
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: LocalText(
|
||||
context,
|
||||
'forget.password',
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
backgroundColor: primaryColor,
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.only(left: 24.0, right: 24.0),
|
||||
children: <Widget>[
|
||||
Form(key: _formKey, child: emailInput),
|
||||
SizedBox(height: 8.0),
|
||||
enterButton,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _forgetPassword() async {
|
||||
var phoneNumber = _email.text;
|
||||
if (phoneNumber.isEmpty) {
|
||||
showMsgDialog(context, "Error", "Please input email(or)phone number");
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
await userModel.forgetPassword(phoneNumber);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ResetPasswordPage(phoneNumber)));
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
119
lib/pages/help.dart
Normal file
119
lib/pages/help.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http_server/http_server.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/vo/setting.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
typedef void ProfileCallback();
|
||||
|
||||
class Help extends StatefulWidget {
|
||||
|
||||
const Help({Key key}) : super(key: key);
|
||||
@override
|
||||
_HelpState createState() => _HelpState();
|
||||
}
|
||||
|
||||
class _HelpState extends State<Help> {
|
||||
final log = Logger('Help');
|
||||
|
||||
bool isLoading = false;
|
||||
HttpServer httpServer;
|
||||
// WebViewController _controller;
|
||||
String url = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
var isBuyer = mainModel.isBuyer();
|
||||
this.url = "http://localhost:7777/web/index.html" +
|
||||
(isBuyer ? "?is_buyer=true" : "?is_buyer=false");
|
||||
_run(mainModel.setting);
|
||||
}
|
||||
|
||||
void _run(Setting setting) async {
|
||||
await _download(setting);
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
var staticFiles = new VirtualDirectory('${directory.path}')
|
||||
..allowDirectoryListing = true;
|
||||
|
||||
HttpServer.bind('0.0.0.0', 7777).then((server) async {
|
||||
httpServer = server;
|
||||
log.info('Server running');
|
||||
server.listen(staticFiles.serveRequest);
|
||||
// _controller.loadUrl(url);
|
||||
log.info("locad url:$url");
|
||||
}, onError: (e) {
|
||||
log.warning("Error===>:$e");
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
if (httpServer != null) await httpServer.close(force: true);
|
||||
}
|
||||
|
||||
Future<void> _download(Setting setting) async {
|
||||
final directory = (await getApplicationDocumentsDirectory()).path;
|
||||
|
||||
var file = File('$directory/${setting.helpFileName()}');
|
||||
if (await file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String url = setting.helpURL;
|
||||
var req = await http.Client().get(Uri.parse(url));
|
||||
File zippedFile = await file.writeAsBytes(req.bodyBytes);
|
||||
|
||||
File prev = File('$directory/web');
|
||||
if (await prev.exists()) {
|
||||
await prev.delete(recursive: true);
|
||||
}
|
||||
|
||||
var bytes = zippedFile.readAsBytesSync();
|
||||
var archive = ZipDecoder().decodeBytes(bytes);
|
||||
for (var file in archive) {
|
||||
var fileName = '$directory/web/${file.name}';
|
||||
if (file.isFile) {
|
||||
var outFile = File(fileName);
|
||||
outFile = await outFile.create(recursive: true);
|
||||
await outFile.writeAsBytes(file.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.black, //change your color here
|
||||
),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
title: ClipRRect(
|
||||
child: Image.asset("assets/logo.png", height: 40),
|
||||
borderRadius: new BorderRadius.circular(15.0),
|
||||
)),
|
||||
body:Text("abc"),
|
||||
// body: WebView(
|
||||
// initialUrl: url,
|
||||
// javascriptMode: JavascriptMode.unrestricted,
|
||||
// onWebViewCreated: (WebViewController webViewController) {
|
||||
// _controller = webViewController;
|
||||
// },
|
||||
// ),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
469
lib/pages/home_page.dart
Normal file
469
lib/pages/home_page.dart
Normal file
@@ -0,0 +1,469 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/charts/bar_chart.dart';
|
||||
import 'package:fcs/charts/delivery_do_line.dart';
|
||||
import 'package:fcs/charts/delivery_do_summary.dart';
|
||||
import 'package:fcs/charts/delivery_line.dart';
|
||||
import 'package:fcs/charts/delivery_summary.dart';
|
||||
import 'package:fcs/charts/do_line.dart';
|
||||
import 'package:fcs/charts/po_balance_chart.dart';
|
||||
import 'package:fcs/charts/po_line.dart';
|
||||
import 'package:fcs/charts/quota.dart';
|
||||
import 'package:fcs/charts/revenue_line.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/notification_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/pages/banks/banks.dart';
|
||||
import 'package:fcs/pages/buyer_list.dart';
|
||||
import 'package:fcs/pages/contact.dart';
|
||||
import 'package:fcs/pages/delivery/delivery_list.dart';
|
||||
import 'package:fcs/pages/manual/manual_page.dart';
|
||||
import 'package:fcs/pages/my_registeration_info.dart';
|
||||
import 'package:fcs/pages/notification_list.dart';
|
||||
import 'package:fcs/pages/pin_login_dialog.dart';
|
||||
import 'package:fcs/pages/settings.dart';
|
||||
import 'package:fcs/pages/term.dart';
|
||||
import 'package:fcs/pages/test_list.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/reports/report_list.dart';
|
||||
import 'package:fcs/vo/user.dart';
|
||||
import 'package:fcs/widget/badge.dart';
|
||||
import 'package:fcs/widget/banner.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/offline_redirect.dart';
|
||||
import 'package:fcs/vo/notification.dart' as Noti;
|
||||
|
||||
import '../theme/theme.dart';
|
||||
import 'announcement_list.dart';
|
||||
import 'do/do_list.dart';
|
||||
import 'employee_list.dart';
|
||||
import 'my_registeration.dart';
|
||||
import 'pd/pd_list.dart';
|
||||
import 'po/po_submission_list.dart';
|
||||
import 'products_list.dart';
|
||||
import 'profile_page.dart';
|
||||
import 'storage/storage_list.dart';
|
||||
import 'user_list.dart';
|
||||
|
||||
final msgLog = Logger('backgroundMessageHandler');
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
}
|
||||
|
||||
typedef BtnCallback();
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
final log = Logger('_HomePageState');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
static final List<String> chartDropdownItems = [
|
||||
'Last 7 days',
|
||||
'Last month',
|
||||
'Last three months'
|
||||
];
|
||||
String actualDropdown = chartDropdownItems[0];
|
||||
int actualChart = 0;
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
String pin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final helpBtn = _buildBtn("manual.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/manual.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => ManualPage()))
|
||||
// btnCallback: () => Navigator.of(context)
|
||||
// .push(MaterialPageRoute(builder: (_) => TestList()))
|
||||
);
|
||||
final announcementBtn = _buildBtn("announcement.title",
|
||||
icon: Icons.announcement,
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => AnnouncementList())));
|
||||
|
||||
final buyerBtn = _buildBtn("buyer.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/buyer.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => BuyerList())));
|
||||
|
||||
final reportBtn = _buildBtn("report.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/report.png",
|
||||
width: 50,
|
||||
height: 50,
|
||||
// color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => ReportList())));
|
||||
|
||||
final myRegBtn = _buildBtn("myreg.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/reg.png",
|
||||
width: 40,
|
||||
height: 30,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () async {});
|
||||
|
||||
final posBtn = _buildBtn("po.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/pay.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () {});
|
||||
|
||||
final dosBtn = _buildBtn("do.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/do.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => DOList())));
|
||||
|
||||
final deliveryBtn = _buildBtn("delivery.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/truck.png",
|
||||
width: 50,
|
||||
height: 50,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => DeliveryList())));
|
||||
|
||||
final storageBtn = _buildBtn("storage.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/inventory.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => StorageList())));
|
||||
|
||||
final pdosBtn = _buildBtn("pd.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/pdo.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => PDList())));
|
||||
|
||||
final employeeBtn = _buildBtn("employee.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/employee.png",
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: primaryColor,
|
||||
),
|
||||
btnCallback: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => EmployeeList())));
|
||||
|
||||
final termBtn = _buildBtn("term.title",
|
||||
imgIcon: Image.asset(
|
||||
"assets/term.png",
|
||||
width: 40,
|
||||
height: 30,
|
||||
color: primaryColor,
|
||||
), btnCallback: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => Term()));
|
||||
});
|
||||
|
||||
final userBtn =
|
||||
_buildBtn("users.title", icon: Icons.group, btnCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => UserList()),
|
||||
);
|
||||
});
|
||||
|
||||
final settingsBtn =
|
||||
_buildBtn("setting.title", icon: Icons.settings, btnCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => Settings()),
|
||||
);
|
||||
});
|
||||
|
||||
final _bankAccountsBtn = _buildBtn("banks.title",
|
||||
icon: FontAwesomeIcons.moneyCheck, btnCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => BankAccounts()),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
List<Widget> widgets = [helpBtn];
|
||||
widgets.add(announcementBtn);
|
||||
widgets.add(reportBtn);
|
||||
widgets.add(termBtn);
|
||||
widgets.add(_bankAccountsBtn);
|
||||
|
||||
var revenueChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[RevenueLineChart()],
|
||||
));
|
||||
|
||||
var productListBox = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
LocalText(context, 'products.prices', color: primaryColor),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, 'products.gas',
|
||||
color: Colors.black,
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w700),
|
||||
Consumer<ProductModel>(builder: (context, model, child) {
|
||||
return Text(' ${model.products.length}',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 19.0));
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Material(
|
||||
color: thirdColor,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: //Icon(Icons.timeline, color: Colors.white, size: 30.0),
|
||||
Image.asset(
|
||||
"assets/product.png",
|
||||
width: 60,
|
||||
height: 70,
|
||||
color: Colors.white,
|
||||
))))
|
||||
]);
|
||||
|
||||
var poqtyByProductChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[BarChart()],
|
||||
));
|
||||
|
||||
var poChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[POLineChart()],
|
||||
));
|
||||
|
||||
var doChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[DOLineChart()],
|
||||
));
|
||||
|
||||
var deliveryChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[DeliveryBarChart()],
|
||||
));
|
||||
|
||||
var deliveryDOChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[DODeliveryLineChart()],
|
||||
));
|
||||
|
||||
var deliveryDoSummary = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[DeliveryDoSummaryChart()],
|
||||
));
|
||||
var deliverySummary = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[DeliverySummary()],
|
||||
));
|
||||
var poBalancebyBuyerChart = Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: <Widget>[POBalanceChart()],
|
||||
));
|
||||
List<Widget> chartWidgets = [];
|
||||
|
||||
return OfflineRedirect(
|
||||
child: FlavorBanner(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: ClipRRect(
|
||||
child: Image.asset("assets/logo.png", height: 40),
|
||||
borderRadius: new BorderRadius.circular(15.0),
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => Contact()),
|
||||
);
|
||||
},
|
||||
iconSize: 30,
|
||||
icon: Icon(Icons.phone),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => Profile()),
|
||||
);
|
||||
},
|
||||
iconSize: 30,
|
||||
icon: Icon(Icons.account_circle),
|
||||
),
|
||||
]),
|
||||
body: StaggeredGridView.count(
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 12.0,
|
||||
mainAxisSpacing: 12.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
children: <Widget>[
|
||||
_buildTile(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: productListBox,
|
||||
),
|
||||
onTap: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => ProductsList())),
|
||||
),
|
||||
new GridView.count(
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
crossAxisCount: 1,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
padding: EdgeInsets.only(bottom: 5, left: 10, right: 5),
|
||||
children: widgets,
|
||||
),
|
||||
_buildTile(
|
||||
PageView(
|
||||
children: chartWidgets,
|
||||
),
|
||||
),
|
||||
],
|
||||
staggeredTiles: [
|
||||
StaggeredTile.extent(5, 110.0),
|
||||
StaggeredTile.extent(3, 110.0),
|
||||
StaggeredTile.extent(3, 250.0),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTile(Widget child, {Function() onTap}) {
|
||||
return Material(
|
||||
elevation: 30.0,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
shadowColor: Color(0x802196F3),
|
||||
child: InkWell(
|
||||
onTap: onTap != null
|
||||
? () => onTap()
|
||||
: () {
|
||||
log.info('Not set yet');
|
||||
},
|
||||
child: child));
|
||||
}
|
||||
|
||||
Widget _buildBtn(String title,
|
||||
{Image imgIcon, IconData icon, BtnCallback btnCallback}) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
return _buildTile(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
icon != null
|
||||
? Material(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Icon(icon, color: primaryColor, size: 35.0)))
|
||||
: Container(
|
||||
padding: EdgeInsets.only(top: 3),
|
||||
child: imgIcon,
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(bottom: 10.0)),
|
||||
Text(AppTranslations.of(context).text(title),
|
||||
style: languageModel.isEng
|
||||
? TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12.0)
|
||||
: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12.0,
|
||||
fontFamily: "MyanmarUnicode")),
|
||||
]),
|
||||
),
|
||||
onTap: btnCallback,
|
||||
);
|
||||
}
|
||||
|
||||
_showNotifications() async {
|
||||
if (!super.mounted) return;
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => NotificationList()),
|
||||
);
|
||||
// Provider.of<NotificationModel>(context, listen: false).seen();
|
||||
}
|
||||
}
|
||||
97
lib/pages/log_list.dart
Normal file
97
lib/pages/log_list.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/log_model.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart';
|
||||
|
||||
class LogList extends StatefulWidget {
|
||||
@override
|
||||
_LogListState createState() => _LogListState();
|
||||
}
|
||||
|
||||
class _LogListState extends State<LogList> {
|
||||
final double dotSize = 15.0;
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy - hh:mm:ss a');
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
LogModel logModel = Provider.of<LogModel>(context);
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: LocalText(
|
||||
context,
|
||||
'log.title',
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
body: new ListView.separated(
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
color: Colors.black,
|
||||
),
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: logModel.logs.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: new EdgeInsets.symmetric(
|
||||
horizontal: 32.0 - dotSize / 2),
|
||||
child: Icon(
|
||||
Icons.message,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
logModel.logs[index].deviceName,
|
||||
style: new TextStyle(
|
||||
fontSize: 15.0,
|
||||
color: Colors.black),
|
||||
),
|
||||
new Text(
|
||||
"Last login at : ${logModel.logs[index].activeTime == null ? "" : dateFormatter.format(logModel.logs[index].activeTime)}",
|
||||
style: new TextStyle(
|
||||
fontSize: 13.0, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
757
lib/pages/login_page.dart
Normal file
757
lib/pages/login_page.dart
Normal file
@@ -0,0 +1,757 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/user_model.dart';
|
||||
import 'package:fcs/pages/reset_password.dart';
|
||||
import 'package:fcs/widget/bubble_indication_painter.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import '../theme/theme.dart' as Theme;
|
||||
import 'forget_password.dart';
|
||||
import 'sms_page.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
LoginPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => new _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
final FocusNode myFocusNodeEmailLogin = FocusNode();
|
||||
final FocusNode myFocusNodePasswordLogin = FocusNode();
|
||||
|
||||
final FocusNode myFocusNodePassword = FocusNode();
|
||||
final FocusNode myFocusNodeEmail = FocusNode();
|
||||
final FocusNode myFocusNodeName = FocusNode();
|
||||
|
||||
TextEditingController loginPhoneController = new TextEditingController();
|
||||
TextEditingController loginPasswordController = new TextEditingController();
|
||||
|
||||
bool _obscureTextLogin = true;
|
||||
bool _obscureTextSignup = true;
|
||||
bool _obscureTextSignupConfirm = true;
|
||||
|
||||
TextEditingController signupPhoneNumberController =
|
||||
new TextEditingController();
|
||||
TextEditingController signupNameController = new TextEditingController();
|
||||
TextEditingController signupPasswordController = new TextEditingController();
|
||||
TextEditingController signupConfirmPasswordController =
|
||||
new TextEditingController();
|
||||
|
||||
PageController _pageController;
|
||||
|
||||
Color left = Colors.black;
|
||||
Color right = Colors.white;
|
||||
final loginFormKey = GlobalKey<FormState>();
|
||||
final signupFormKey = GlobalKey<FormState>();
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.grey,
|
||||
),
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: new Image(
|
||||
height: 30,
|
||||
fit: BoxFit.scaleDown,
|
||||
image: new AssetImage('assets/img/logo.png')),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height >= 775.0
|
||||
? MediaQuery.of(context).size.height
|
||||
: 580.0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 50.0),
|
||||
child: _buildMenuBar(context),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
onPageChanged: (i) {
|
||||
if (i == 0) {
|
||||
setState(() {
|
||||
right = Colors.white;
|
||||
left = Colors.black;
|
||||
});
|
||||
} else if (i == 1) {
|
||||
setState(() {
|
||||
right = Colors.black;
|
||||
left = Colors.white;
|
||||
});
|
||||
}
|
||||
},
|
||||
children: <Widget>[
|
||||
new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: _buildLogin(context),
|
||||
),
|
||||
new ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
child: _buildSignUp(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myFocusNodePassword.dispose();
|
||||
myFocusNodeEmail.dispose();
|
||||
myFocusNodeName.dispose();
|
||||
_pageController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// SystemChrome.setPreferredOrientations([
|
||||
// DeviceOrientation.portraitUp,
|
||||
// DeviceOrientation.portraitDown,
|
||||
// ]);
|
||||
|
||||
_pageController = PageController();
|
||||
loginPhoneController.text = "09";
|
||||
signupPhoneNumberController.text = "09";
|
||||
}
|
||||
|
||||
Widget _buildMenuBar(BuildContext context) {
|
||||
return Container(
|
||||
width: 300.0,
|
||||
height: 40.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0x552B2B2B),
|
||||
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: TabIndicationPainter(pageController: _pageController),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: FlatButton(
|
||||
onPressed: _onSignInButtonPress,
|
||||
child: Text(
|
||||
AppTranslations.of(context).text("login.title"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
color: left,
|
||||
fontSize: 14.0,
|
||||
fontFamily: "WorkSansSemiBold")
|
||||
: TextStyle(
|
||||
color: left,
|
||||
fontSize: 15.0,
|
||||
fontFamily: "MyanmarUnicode"),
|
||||
),
|
||||
),
|
||||
),
|
||||
//Container(height: 33.0, width: 1.0, color: Colors.white),
|
||||
Expanded(
|
||||
child: FlatButton(
|
||||
onPressed: _onSignUpButtonPress,
|
||||
child: Text(
|
||||
AppTranslations.of(context).text("sing.title"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
color: right,
|
||||
fontSize: 14.0,
|
||||
fontFamily: "WorkSansSemiBold")
|
||||
: TextStyle(
|
||||
color: right,
|
||||
fontSize: 15.0,
|
||||
fontFamily: "MyanmarUnicode"),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogin(BuildContext context) {
|
||||
return Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: loginFormKey,
|
||||
child: Card(
|
||||
elevation: 2.0,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Container(
|
||||
width: 300.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodeEmailLogin,
|
||||
controller: loginPhoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.phone,
|
||||
color: Colors.black,
|
||||
size: 22.0,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.phone"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
),
|
||||
validator: _validatePhone,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodePasswordLogin,
|
||||
controller: loginPasswordController,
|
||||
obscureText: _obscureTextLogin,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.lock,
|
||||
size: 22.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.password"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: _toggleLogin,
|
||||
child: Icon(
|
||||
_obscureTextLogin
|
||||
? FontAwesomeIcons.eye
|
||||
: FontAwesomeIcons.eyeSlash,
|
||||
size: 15.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: _validatePassword,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.primaryColor,
|
||||
),
|
||||
child: MaterialButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0, horizontal: 42.0),
|
||||
child: Text(
|
||||
AppTranslations.of(context).text("login.btn"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "WorkSansBold")
|
||||
: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "MyanmarUnicode"),
|
||||
),
|
||||
),
|
||||
onPressed: () => _login(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: new LinearGradient(
|
||||
colors: [
|
||||
Colors.white10,
|
||||
Colors.white,
|
||||
],
|
||||
begin: const FractionalOffset(0.0, 0.0),
|
||||
end: const FractionalOffset(1.0, 1.0),
|
||||
stops: [0.0, 1.0],
|
||||
tileMode: TileMode.clamp),
|
||||
),
|
||||
width: 100.0,
|
||||
height: 1.0,
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: new LinearGradient(
|
||||
colors: [
|
||||
Colors.white,
|
||||
Colors.white10,
|
||||
],
|
||||
begin: const FractionalOffset(0.0, 0.0),
|
||||
end: const FractionalOffset(1.0, 1.0),
|
||||
stops: [0.0, 1.0],
|
||||
tileMode: TileMode.clamp),
|
||||
),
|
||||
width: 100.0,
|
||||
height: 1.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ForgetPassword(
|
||||
phoneNumber: loginPhoneController.text,
|
||||
)));
|
||||
},
|
||||
child: Text(
|
||||
AppTranslations.of(context).text("login.forgot_password"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
fontFamily: "WorkSansMedium")
|
||||
: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
fontFamily: "MyanmarUnicode"),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSignUp(BuildContext context) {
|
||||
return Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: signupFormKey,
|
||||
child: Card(
|
||||
elevation: 2.0,
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Container(
|
||||
width: 300.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodeName,
|
||||
controller: signupNameController,
|
||||
keyboardType: TextInputType.text,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.user,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.name"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
hintStyle: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context)
|
||||
.text("login.name_empty");
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodeEmail,
|
||||
controller: signupPhoneNumberController,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.phone,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.phone"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
),
|
||||
validator: _validatePhone),
|
||||
),
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
focusNode: myFocusNodePassword,
|
||||
controller: signupPasswordController,
|
||||
obscureText: _obscureTextSignup,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.lock,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.password"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: _toggleSignup,
|
||||
child: Icon(
|
||||
_obscureTextSignup
|
||||
? FontAwesomeIcons.eye
|
||||
: FontAwesomeIcons.eyeSlash,
|
||||
size: 15.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: _validatePassword,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 250.0,
|
||||
height: 1.0,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: TextFormField(
|
||||
controller: signupConfirmPasswordController,
|
||||
obscureText: _obscureTextSignupConfirm,
|
||||
style: TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
fontSize: 16.0,
|
||||
color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
icon: Icon(
|
||||
FontAwesomeIcons.lock,
|
||||
color: Colors.black,
|
||||
),
|
||||
labelText: AppTranslations.of(context)
|
||||
.text("login.confirm_password"),
|
||||
labelStyle:
|
||||
Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
fontFamily: "WorkSansSemiBold",
|
||||
color: Colors.grey)
|
||||
: TextStyle(
|
||||
fontFamily: "MyanmarUnicode",
|
||||
color: Colors.grey),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: _toggleSignupConfirm,
|
||||
child: Icon(
|
||||
_obscureTextSignupConfirm
|
||||
? FontAwesomeIcons.eye
|
||||
: FontAwesomeIcons.eyeSlash,
|
||||
size: 15.0,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: _validatePassword,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
// margin: EdgeInsets.only(top: 320.0),
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.primaryColor,
|
||||
),
|
||||
child: MaterialButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Theme.LoginColors.loginGradientEnd,
|
||||
//shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0, horizontal: 42.0),
|
||||
child: Text(
|
||||
AppTranslations.of(context).text("sing.up"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18.0,
|
||||
fontFamily: "WorkSansBold")
|
||||
: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
fontFamily: "MyanmarUnicode"),
|
||||
),
|
||||
),
|
||||
onPressed: () => _signup(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSignInButtonPress() {
|
||||
_pageController.animateToPage(0,
|
||||
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
|
||||
}
|
||||
|
||||
void _onSignUpButtonPress() {
|
||||
_pageController?.animateToPage(1,
|
||||
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
|
||||
}
|
||||
|
||||
void _toggleLogin() {
|
||||
setState(() {
|
||||
_obscureTextLogin = !_obscureTextLogin;
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleSignup() {
|
||||
setState(() {
|
||||
_obscureTextSignup = !_obscureTextSignup;
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleSignupConfirm() {
|
||||
setState(() {
|
||||
_obscureTextSignupConfirm = !_obscureTextSignupConfirm;
|
||||
});
|
||||
}
|
||||
|
||||
void _signup(BuildContext context) async {
|
||||
if (!signupFormKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
MainModel authModel = Provider.of<MainModel>(context);
|
||||
|
||||
var name = signupNameController.text;
|
||||
var password = signupPasswordController.text;
|
||||
var confirmPassword = signupConfirmPasswordController.text;
|
||||
var phoneNumber = signupPhoneNumberController.text;
|
||||
try {
|
||||
await authModel.signup(name, password, confirmPassword, phoneNumber);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
SmsCodePage(id: phoneNumber, password: password),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _login(BuildContext context) async {
|
||||
if (!loginFormKey.currentState.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
MainModel mainModel = Provider.of<MainModel>(context);
|
||||
var phoneNumber = loginPhoneController.text;
|
||||
var password = loginPasswordController.text;
|
||||
|
||||
try {
|
||||
await mainModel.login(phoneNumber, password);
|
||||
Navigator.pushNamedAndRemoveUntil(context, "/", (r) => false);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
}
|
||||
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String _validatePassword(value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("login.password_empty");
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return AppTranslations.of(context).text("login.password_size");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _validatePhone(value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("login.phone_empty");
|
||||
}
|
||||
if (!value.startsWith("09") && !value.startsWith("959")) {
|
||||
return 'Only "09 or 959".';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _forgetPassword() async {
|
||||
var phoneNumber = loginPhoneController.text;
|
||||
if (phoneNumber.isEmpty) {
|
||||
showMsgDialog(context, "Error", "Please input phone number");
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
UserModel userModel = Provider.of<UserModel>(context);
|
||||
await userModel.forgetPassword(phoneNumber);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ResetPasswordPage(phoneNumber)));
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
227
lib/pages/manual/instruction_data_page.dart
Normal file
227
lib/pages/manual/instruction_data_page.dart
Normal file
@@ -0,0 +1,227 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/manual_model.dart';
|
||||
import 'package:fcs/pages/manual/moveable_stack_item.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/manual.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
class InstructionDataPage extends StatefulWidget {
|
||||
final SlideData slideData;
|
||||
final String path;
|
||||
final int slideIndex;
|
||||
final int manIndex;
|
||||
|
||||
InstructionDataPage(
|
||||
{this.slideData, this.path, this.slideIndex, this.manIndex});
|
||||
@override
|
||||
_InstructionDataPageState createState() => _InstructionDataPageState();
|
||||
}
|
||||
|
||||
class _InstructionDataPageState extends State<InstructionDataPage> {
|
||||
String selectedLanguage;
|
||||
File slideImageFile;
|
||||
bool isEng;
|
||||
List<bool> _selection = List.generate(2, (_) => false);
|
||||
String imgName;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isEng = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var manualModel = Provider.of<ManualModel>(context);
|
||||
if (isEng) {
|
||||
imgName = widget.slideData.image;
|
||||
} else {
|
||||
imgName = widget.slideData.imagemm;
|
||||
}
|
||||
|
||||
var screenSize = MediaQuery.of(context).size;
|
||||
var width = screenSize.width;
|
||||
var imgWidth = width - (width * 0.15);
|
||||
var height = screenSize.height;
|
||||
var imgHeight = height - (height * 0.29);
|
||||
|
||||
var toggleButtons = Container(
|
||||
child: ToggleButtons(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
"assets/eng_flag.png",
|
||||
width: 25,
|
||||
),
|
||||
Text(
|
||||
"English",
|
||||
style:
|
||||
TextStyle(color: this.isEng ? secondaryColor : Colors.black),
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10, right: 10),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
"assets/myan_flag.png",
|
||||
width: 25,
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
"မြန်မာ",
|
||||
style: TextStyle(
|
||||
color: !this.isEng ? secondaryColor : Colors.black,
|
||||
fontFamily: "MyanmarUnicode"),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
renderBorder: false,
|
||||
isSelected: _selection,
|
||||
selectedColor: secondaryColor,
|
||||
onPressed: (int index) {
|
||||
setState(() {
|
||||
_selection[index] = !_selection[index];
|
||||
if (index == 0) {
|
||||
this.isEng = true;
|
||||
} else {
|
||||
this.isEng = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
));
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Instruction Data'),
|
||||
actions: <Widget>[toggleButtons],
|
||||
),
|
||||
body: Container(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Stack(
|
||||
children: instructionData(
|
||||
context, imgWidth, imgHeight, manualModel),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.image),
|
||||
onPressed: () {
|
||||
pickImageFromGallery(ImageSource.gallery, manualModel);
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
addInstructionData();
|
||||
},
|
||||
tooltip: 'Pick Image',
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
addInstructionData() {
|
||||
var instructionList =
|
||||
isEng ? widget.slideData.instructions : widget.slideData.instructionsmm;
|
||||
Instruction inst;
|
||||
if (instructionList.toList().length == 0) {
|
||||
inst = Instruction(
|
||||
id: 1,
|
||||
text: '',
|
||||
left: 0.0,
|
||||
top: 0.0,
|
||||
);
|
||||
} else {
|
||||
dynamic max = instructionList.first;
|
||||
instructionList.forEach((e) {
|
||||
if (e.id > max.id) max = e;
|
||||
});
|
||||
inst = Instruction(
|
||||
id: max.id + 1,
|
||||
text: '',
|
||||
left: 0.0,
|
||||
top: 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (isEng) {
|
||||
widget.slideData.instructions.add(inst);
|
||||
} else {
|
||||
widget.slideData.instructionsmm.add(inst);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
instructionData(
|
||||
BuildContext context, double imgW, double imgH, ManualModel manualModel) {
|
||||
List<Widget> textFields = [];
|
||||
textFields.add(
|
||||
Image.file(
|
||||
File('${widget.path}/manual/img/' + imgName),
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
height: imgH,
|
||||
width: imgW,
|
||||
),
|
||||
);
|
||||
var instructionList =
|
||||
isEng ? widget.slideData.instructions : widget.slideData.instructionsmm;
|
||||
if (instructionList.length != 0) {
|
||||
instructionList.asMap().forEach((k, instruction) {
|
||||
MoveableStackItem mitem = MoveableStackItem(
|
||||
instruction: instruction,
|
||||
manIndex: widget.manIndex,
|
||||
slideIndex: widget.slideIndex,
|
||||
instIndex: k,
|
||||
isEng: this.isEng,
|
||||
key: Key(this.isEng.toString() + k.toString()),
|
||||
);
|
||||
|
||||
textFields.add(mitem);
|
||||
});
|
||||
}
|
||||
return textFields;
|
||||
}
|
||||
|
||||
pickImageFromGallery(ImageSource source, ManualModel manualModel) async {
|
||||
File tempImage = await ImagePicker.pickImage(
|
||||
source: source, imageQuality: 80, maxWidth: 300);
|
||||
|
||||
var fileName = Path.basename(tempImage.path);
|
||||
var path = '${manualModel.dataDir}';
|
||||
File newImage = await tempImage.copy('$path/manual/img/$fileName');
|
||||
var slideData = widget.slideData;
|
||||
if (this.isEng) {
|
||||
slideData.image = fileName;
|
||||
} else {
|
||||
slideData.imagemm = fileName;
|
||||
}
|
||||
|
||||
manualModel.changeSlideImage(widget.manIndex, widget.slideIndex, slideData);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user