add structure

This commit is contained in:
2020-05-29 07:45:27 +06:30
parent 4c851d9971
commit bad27ba5c4
272 changed files with 36065 additions and 174 deletions

163
lib/app.dart Normal file
View 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
View 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 ?? [];
});
}
}

View 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],
))),
),
),
],
),
);
}
}

View 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();
}
}

View 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,
),
),
),
),
],
),
);
}
}

View 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();
}
}

View 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,
// ),
// ),
// ),
// ),
],
),
);
}
}

View 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();
}
}

View 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();
}
}

View 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,
),
),
),
),
],
),
);
}
}

View 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
View 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],
))),
),
),
],
),
);
}
}

View 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
View 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);
}

View 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 ?? [];
});
}
}
}

View 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();
}
}

View 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
View 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],
))),
),
),
],
),
);
}
}

View 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();
}
}

View 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
View 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);
}

View 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],
))),
),
),
],
),
);
}
}

View 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();
}
}

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

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

View 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
View 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
View 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
View 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
View 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
View 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());
}

View File

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

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

View 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();
});
}
}
}

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

View 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);
}
}
}

View 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");
}
}

View 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
View 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
View 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
View 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
View 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);
}
}

View 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());
}
}

View File

@@ -0,0 +1,4 @@
class Listener {
}

88
lib/model/pd_model.dart Normal file
View 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
View 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;
}
}

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

View 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();
}
}

View 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);
}
}

View 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
View 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
View 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();
});
}
}

View 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);
}
}

View 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
View 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;
});
}
}
}

View 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);
});
}
}
}

View 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)),
],
)
],
),
),
),
],
),
);
}),
),
);
}
}

View 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
View 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
View 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
View 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
View 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],
);
}),
),
);
}
}

View 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),
),
],
)
],
),
),
),
);
}
}

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

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

View 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
View 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"]);
}
}

View 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;
});
}
}
}

View 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;
});
}
}
}

View 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
View 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;
});
}
}
}

View 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;
});
}
}
}

View 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;
});
}
}
}

View 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;
});
}
}

View 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
View 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),
),
],
),
),
),
),
],
),
);
}),
),
);
}
}

View 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);
}
}

View 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);
}
}

View 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;
});
}
}

View 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]);
}
});
}
}

View 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
View 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;
});
}
}
}

View 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;
});
}
}
}

View 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)}")
],
),
],
),
),
],
),
),
);
}),
),
);
}
}

View 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
View 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
View 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
View 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
View 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;
});
}
}
}

View 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