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

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

View File

@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/manual_model.dart';
import 'package:fcs/vo/manual.dart';
class ManualItemTitleDialog extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _ManualItemTitleDialogState();
}
}
class _ManualItemTitleDialogState extends State<ManualItemTitleDialog> {
TextEditingController _engTextFieldController = TextEditingController();
TextEditingController _mmTextFieldController = TextEditingController();
ManualItem item;
bool buyer = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: new Text('Enter Title'),
content: Container(
height: 200,
child: Column(
children: <Widget>[
TextField(
controller: _engTextFieldController,
decoration: InputDecoration(hintText: "Enter English"),
maxLines: null,
style: TextStyle(fontSize: 13.0),
),
TextField(
controller: _mmTextFieldController,
decoration: InputDecoration(hintText: "Enter Myanmar"),
maxLines: null,
style: TextStyle(fontSize: 13.0),
),
CheckboxListTile(
title: Text("For Buyer"),
value: buyer,
onChanged: (val) {
setState(() {
buyer = val;
});
},
),
],
),
),
actions: <Widget>[
new FlatButton(
onPressed: () {
_save();
},
child: new Text('Save'))
],
);
}
_save() {
try {
String eng = _engTextFieldController.text;
String mm = _mmTextFieldController.text;
ManualItem item =
ManualItem(title: eng, titlemm: mm, isBuyer: buyer, slides: []);
Navigator.pop<ManualItem>(context, item);
} catch (e) {
// showMsgDialog(context, "Error", e.toString());
}
}
}

View File

@@ -0,0 +1,234 @@
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/manual_model.dart';
import 'package:fcs/pages/manual/manual_item_title_dialog.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/manual.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
import 'slide_page.dart';
class ManualPage extends StatefulWidget {
@override
_ManualPageState createState() => _ManualPageState();
}
class _ManualPageState extends State<ManualPage> {
TextEditingController _manualVersionController = TextEditingController();
final double dotSize = 10.0;
List<ManualItem> helpList = new List();
bool isEng;
String versionName;
bool _isLoading = false;
@override
void initState() {
helpList.clear();
var manualModel = Provider.of<ManualModel>(context, listen: false);
var mainModel = Provider.of<MainModel>(context, listen: false);
versionName = manualModel.version;
helpList = manualModel.getHelpList(mainModel.isBuyer());
super.initState();
}
@override
Widget build(BuildContext context) {
var manualModel = Provider.of<ManualModel>(context);
var mainModel = Provider.of<MainModel>(context);
var languageModel = Provider.of<LanguageModel>(context);
isEng = languageModel.isEng;
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
actions: <Widget>[
mainModel.isSysAdmin()
? Row(
children: <Widget>[
Text(
versionName,
style: TextStyle(fontSize: 15, color: Colors.black),
textAlign: TextAlign.center,
),
IconButton(
icon: Icon(Icons.refresh),
color: Colors.blue,
onPressed: () {
setState(() {
manualModel.resetManualItems();
helpList =
manualModel.getHelpList(mainModel.isBuyer());
});
}),
IconButton(
icon: Icon(Icons.cloud_upload),
color: Colors.blue,
onPressed: () {
_inputManualVersion(context, manualModel);
})
],
)
: Container(),
],
iconTheme: IconThemeData(
color: Colors.grey,
),
centerTitle: true,
title: Stack(
children: <Widget>[
LocalText(
context,
"manual.title",
fontSize: 25,
),
],
),
),
body: Column(
children: <Widget>[
new Expanded(
child: new ListView.builder(
scrollDirection: Axis.vertical,
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
shrinkWrap: true,
itemCount: helpList.length,
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 10,
color: Colors.white,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SlidePage(
helpDetail: helpList[index],
index: index,
)),
);
},
child: Row(children: <Widget>[
Expanded(
child: new Padding(
padding:
const EdgeInsets.symmetric(vertical: 5.0),
child: new Row(
children: <Widget>[
new Padding(
padding: new EdgeInsets.symmetric(
horizontal: 15.0 - dotSize / 2),
child: Container(
child: Image.asset(
"assets/page.png",
width: 50,
height: 50,
)),
),
new Expanded(
child: new Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
isEng
? helpList[index].title
: helpList[index].titlemm,
style: TextStyle(
fontSize: 15,
color: Colors.grey),
)
],
),
),
],
),
),
),
mainModel.isSysAdmin()
? Container(
padding: EdgeInsets.only(right: 20),
child: InkWell(
onTap: () {
showConfirmDialog(
context, "manual.confirm", () {
setState(() {
manualModel.deleteManualItem(
helpList[index]);
helpList = manualModel.getHelpList(
mainModel.isBuyer());
});
});
},
child: Icon(Icons.delete),
))
: Container()
])),
);
}),
),
],
),
floatingActionButton: mainModel.isSysAdmin()
? FloatingActionButton(
onPressed: () async {
ManualItem newItem = await showDialog(
context: context,
builder: (_) => ManualItemTitleDialog());
if (helpList.toList().length == 0) {
newItem.id = 1;
} else {
dynamic max = helpList.first;
helpList.forEach((e) {
if (e.id > max.id) max = e;
});
newItem.id = max.id + 1;
}
setState(() {
helpList.add(newItem);
});
manualModel.addManualTitle(newItem);
},
child: Icon(Icons.add),
)
: null,
),
);
}
_deleteManualItem(ManualItem item, ManualModel manualModel) {
return showConfirmDialog(context, "manual.confirm", () {
setState(() {
manualModel.deleteManualItem(item);
});
});
}
_inputManualVersion(BuildContext context, ManualModel manModel) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: new Text('Version'),
content: TextField(
controller: _manualVersionController,
decoration: InputDecoration(hintText: "Enter manual version"),
),
actions: <Widget>[
new FlatButton(
onPressed: () {
String version = _manualVersionController.text;
String dir = manModel.dataDir;
manModel.uploadStorageManualData(version, dir);
Navigator.of(context).pop();
},
child: new Text('Save'))
],
);
});
}
}

View File

@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/manual_model.dart';
import 'package:fcs/vo/manual.dart';
class MoveableStackItem extends StatefulWidget {
Instruction instruction;
final int instIndex;
final int slideIndex;
final int manIndex;
final bool isEng;
MoveableStackItem(
{this.instIndex,
this.slideIndex,
this.manIndex,
this.instruction,
this.isEng,
Key key})
: super(key: key);
@override
State<StatefulWidget> createState() {
return _MoveableStackItemState();
}
}
class _MoveableStackItemState extends State<MoveableStackItem> {
TextEditingController _textFieldController = TextEditingController();
double xPosition = 0;
double yPosition = 0;
@override
void initState() {
super.initState();
_textFieldController.text = widget.instruction.text;
yPosition =
widget.instruction.top != null ? widget.instruction.top : yPosition;
xPosition =
widget.instruction.left != null ? widget.instruction.left : xPosition;
}
@override
Widget build(BuildContext context) {
var manualModel = Provider.of<ManualModel>(context);
return Positioned(
top: yPosition,
left: xPosition,
child: GestureDetector(
onPanUpdate: (tapInfo) {
setState(() {
xPosition += tapInfo.delta.dx;
yPosition += tapInfo.delta.dy;
});
var data = Instruction(
id: widget.instruction.id,
top: yPosition,
left: xPosition,
text: _textFieldController.text,
);
manualModel.saveInstruction(widget.manIndex, widget.slideIndex,
widget.instIndex, data, widget.instruction, widget.isEng);
},
child: Container(
width: 250,
color: Colors.grey,
child: InkWell(
onTap: () {
_displayDialog(context, manualModel);
},
child: Container(
child: Text(
_textFieldController.text,
maxLines: null,
style:
TextStyle(fontSize: 15.0, fontWeight: FontWeight.w700),
))))),
);
}
_displayDialog(BuildContext context, ManualModel manualModel) async {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: new Text('Instruction'),
content: Container(
height: 100,
child: Column(
children: <Widget>[
TextField(
controller: _textFieldController,
decoration: InputDecoration(hintText: "Enter Instruction"),
maxLines: null,
style: TextStyle(fontSize: 13.0),
),
],
),
),
actions: <Widget>[
new FlatButton(
onPressed: () {
var data = Instruction(
id: widget.instruction.id,
top: yPosition,
left: xPosition,
text: _textFieldController.text,
);
manualModel.saveInstruction(
widget.manIndex,
widget.slideIndex,
widget.instIndex,
data,
widget.instruction,
widget.isEng);
Navigator.pop(context);
},
child: new Text('Save'))
],
);
});
}
}

View File

@@ -0,0 +1,210 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as Path;
import 'package:provider/provider.dart';
import 'package:fcs/model/manual_model.dart';
import 'package:fcs/pages/manual/instruction_data_page.dart';
import 'package:fcs/vo/manual.dart';
import 'package:fcs/widget/img_file.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
class SlideDataPage extends StatefulWidget {
final ManualItem manItem;
final int manIndex;
SlideDataPage({this.manItem, this.manIndex});
@override
_SlideDataPageState createState() => _SlideDataPageState();
}
class _SlideDataPageState extends State<SlideDataPage> {
File slideImageFile;
List<SlideData> slideList = [];
List<SlideData> _initSlideList = [];
File imageFile;
File engImgFile;
File mmImgFile;
bool _isLoading = false;
@override
void initState() {
super.initState();
}
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 =
SlideData(id: slideList.length, image: fileName, instructions: []);
setState(() {
slideList.add(slideData);
});
// manualModel.saveSlideData(widget.manIndex, slideList.length, slideData);
}
@override
Widget build(BuildContext context) {
var manualModel = Provider.of<ManualModel>(context);
slideList = manualModel.getSlideList(widget.manIndex);
final engImgBox = Container(
padding: EdgeInsets.only(left: 20),
child: Column(children: <Widget>[
Text('English image'),
ImageFile(
imageSource: ImageSource.gallery,
enabled: true,
title: "Image",
onFile: (file) {
engImgFile = file;
})
]));
final mmImgBox = Container(
padding: EdgeInsets.only(left: 20, top: 20),
child: Column(children: <Widget>[
Text('Myanmar image'),
ImageFile(
imageSource: ImageSource.gallery,
enabled: true,
title: "Image",
onFile: (file) {
mmImgFile = file;
}),
]));
final saveImages = Container(
padding: EdgeInsets.only(left: 20, top: 20),
child: FlatButton(
onPressed: () {
saveSlideImages(context, manualModel, widget.manIndex);
},
child: Text('Save')));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: Text('Slide Data'),
),
body: Container(
child: GridView.builder(
itemCount: slideList.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InstructionDataPage(
slideData: slideList[index],
path: manualModel.dataDir,
slideIndex: index,
manIndex: widget.manIndex,
)))
},
child: Container(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.file(File('${manualModel.dataDir}/manual/img/' +
slideList[index].image)),
Positioned(
top: 0,
right: 0,
child: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
manualModel.deleteSlideData(
widget.manIndex, slideList[index]);
});
}),
)
],
),
),
),
);
},
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async => await _dialog(
context, manualModel, engImgBox, mmImgBox, saveImages),
tooltip: 'Pick Image',
child: Icon(Icons.add),
),
),
);
}
Future<void> _dialog(BuildContext context, ManualModel manualModel,
Widget engImg, Widget mmImg, Widget saveImgs) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[engImg, mmImg, saveImgs],
),
],
),
),
),
);
},
);
}
saveSlideImages(
BuildContext context, ManualModel manualModel, int manIndex) async {
var engFileName = Path.basename(engImgFile.path);
var mmFileName = Path.basename(mmImgFile.path);
var path = '${manualModel.dataDir}';
File engImage = await engImgFile.copy('$path/manual/img/$engFileName');
File mmImage = await mmImgFile.copy('$path/manual/img/$mmFileName');
var length = slideList.toList().length;
var slideData;
if (length == 0) {
slideData = SlideData(
id: 1,
image: engFileName,
imagemm: mmFileName,
instructions: [],
instructionsmm: []);
} else {
dynamic max = slideList.first;
slideList.forEach((e) {
if (e.id > max.id) max = e;
});
slideData = SlideData(
id: max.id + 1,
image: engFileName,
imagemm: mmFileName,
instructions: [],
instructionsmm: []);
}
manualModel.saveSlideData(manIndex, length, slideData);
Navigator.pop(context);
}
}

View File

@@ -0,0 +1,249 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:introduction_screen/introduction_screen.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/slide_data_page.dart';
import 'package:fcs/vo/manual.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
class SlidePage extends StatefulWidget {
final ManualItem helpDetail;
final int index;
SlidePage({this.helpDetail, this.index});
@override
_SlidePageState createState() => _SlidePageState();
}
class _SlidePageState extends State<SlidePage> {
bool isEng;
bool _isLoading = false;
void _onIntroEnd(context) {
Navigator.pop(context);
}
List<Widget> instructionText(BuildContext context, isEng, image, imgHeight,
width, List<Instruction> instructions) {
List<Widget> list = new List<Widget>();
var imgWidth = width - (width * 0.25);
File imgFile = File(image);
list.add(
Card(
color: const Color(0x7f7c94b6),
child: Opacity(
opacity: 0.6,
child: Image.file(
imgFile,
alignment: AlignmentDirectional.topCenter,
height: imgHeight,
width: imgWidth,
)),
),
);
for (var i = 0; i < instructions.length; i++) {
var instruction = instructions[i];
var textPositionTop = (imgHeight / 480) * instruction.top;
var textPositionLeft = (imgWidth / 360) * instruction.left;
list.add(Positioned(
top: double.parse(textPositionTop.toString()),
left: double.parse(textPositionLeft.toString()),
child: instruction.text.length > 1
? Container(
constraints: BoxConstraints(maxWidth: 300),
child: Card(
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(3.0),
child: Text(
instruction.text,
style: TextStyle(color: Colors.white),
),
),
))
: Container(
constraints: BoxConstraints(maxWidth: 200),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
alignment: Alignment.topCenter,
width: 30,
height: 30,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.blue),
child: isEng
? Container(
padding: EdgeInsets.only(top: 5),
child: Text(
instruction.text,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white, fontSize: 14.0),
),
)
: Text(
instruction.text,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white, fontSize: 14.0),
),
),
],
),
),
));
}
return list;
}
@override
Widget build(BuildContext context) {
var mainModel = Provider.of<MainModel>(context);
var languageModel = Provider.of<LanguageModel>(context);
var manualModel = Provider.of<ManualModel>(context);
isEng = languageModel.isEng;
List<PageViewModel> pageViews = new List();
var screenSize = MediaQuery.of(context).size;
var width = screenSize.width;
var height = screenSize.height;
var imgHeight = height - (height * 0.25);
const bodyStyle = TextStyle(fontSize: 19.0);
const pageDecoration = const PageDecoration(
titleTextStyle: TextStyle(fontSize: 28.0, fontWeight: FontWeight.w700),
bodyTextStyle: bodyStyle,
descriptionPadding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
pageColor: Colors.white,
imagePadding: EdgeInsets.zero,
);
var pageSlides = widget.helpDetail.slides;
if (pageSlides.length == 0) {
pageViews.add(
PageViewModel(
titleWidget: Row(
children: <Widget>[
Text(
isEng ? widget.helpDetail.title : widget.helpDetail.titlemm,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.black,
),
),
mainModel.isSysAdmin()
? FlatButton(
textColor: Colors.blue,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SlideDataPage(
manItem: widget.helpDetail,
manIndex: widget.index)),
);
},
child: Icon(Icons.edit),
shape: CircleBorder(
side: BorderSide(color: Colors.transparent)),
)
: Container(),
],
),
bodyWidget: Container(
width: width,
alignment: Alignment.center,
),
decoration: pageDecoration),
);
}
for (var i = 0; i < pageSlides.length; i++) {
var instructions;
if (isEng) {
instructions = pageSlides[i].instructions;
} else {
instructions = pageSlides[i].instructionsmm;
}
var imageName;
if (isEng) {
imageName = '${manualModel.dataDir}/manual/img/' + pageSlides[i].image;
} else {
imageName =
'${manualModel.dataDir}/manual/img/' + pageSlides[i].imagemm;
}
pageViews.add(
PageViewModel(
titleWidget: Row(
children: <Widget>[
Text(
isEng ? widget.helpDetail.title : widget.helpDetail.titlemm,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.black,
),
),
mainModel.isSysAdmin()
? FlatButton(
textColor: Colors.blue,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SlideDataPage(
manItem: widget.helpDetail,
manIndex: widget.index)),
);
},
child: Icon(Icons.edit),
shape: CircleBorder(
side: BorderSide(color: Colors.transparent)),
)
: Container(),
],
),
bodyWidget: Container(
width: width,
alignment: Alignment.center,
child: Stack(
alignment: AlignmentDirectional.topStart,
children: instructionText(
context, isEng, imageName, imgHeight, width, instructions),
),
),
decoration: pageDecoration),
);
}
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
body: IntroductionScreen(
pages: pageViews,
onDone: () => _onIntroEnd(context),
showSkipButton: true,
skipFlex: 0,
nextFlex: 0,
skip: const Text('Skip'),
next: const Icon(Icons.arrow_forward),
done: const Text('Done', style: TextStyle(fontWeight: FontWeight.w600)),
dotsDecorator: DotsDecorator(spacing: EdgeInsets.all(1.0)),
)),
);
}
}

View File

@@ -0,0 +1,316 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/reg_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/buyer.dart';
import 'package:fcs/widget/img_file.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/my_data_table.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
import '../widget/localization/app_translations.dart';
import 'my_registeration_item.dart';
enum BuyerType { shop, agent }
class MyRegisteration extends StatefulWidget {
final Buyer buyer;
const MyRegisteration({this.buyer});
@override
_MyRegisterationState createState() => _MyRegisterationState();
}
class _MyRegisterationState extends State<MyRegisteration> {
TextEditingController _bizName = new TextEditingController();
TextEditingController _bizAddress = new TextEditingController();
BuyerType buyerType;
Buyer buyer = Buyer();
final formKey = GlobalKey<FormState>();
bool _isLoading = false;
Attachments attachments = Attachments();
bool _isNew = true;
@override
void initState() {
if (widget.buyer != null) {
_isNew = false;
buyerType =
widget.buyer.bizType == "shop" ? BuyerType.shop : BuyerType.agent;
buyer = widget.buyer;
_bizName.text = buyer.bizName;
_bizAddress.text = buyer.bizAddress;
} else {
buyerType = BuyerType.shop;
}
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
final companyName = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 15),
child: TextFormField(
controller: _bizName,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("reg.biz_name"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
Icons.business,
color: primaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
),
validator: _validateBizName,
));
final companyAddress = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 15),
child: TextFormField(
minLines: 2,
maxLines: 3,
controller: _bizAddress,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("reg.biz_address"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Image.asset(
"assets/address.png",
height: 25,
color: primaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
),
validator: _validateBizAddress,
));
final nricFrontBox = Container(
padding: EdgeInsets.only(left: 20),
child: Row(children: <Widget>[
LocalText(context, 'reg_info.nric_front'),
ImageFile(
enabled: true,
initialImgUrl:
widget.buyer == null ? "" : widget.buyer.nricFrontUrl,
title: "Image",
onFile: (file) {
attachments.nricFront = file;
})
]));
final nricBackBox = Container(
padding: EdgeInsets.only(left: 20, top: 20),
child: Row(children: <Widget>[
LocalText(context, 'reg_info.nric_back'),
ImageFile(
enabled: true,
initialImgUrl:
widget.buyer == null ? '' : widget.buyer.nricBackUrl,
title: "Image",
onFile: (file) {
attachments.nricBack = file;
}),
]));
final nric = Container(
padding: EdgeInsets.only(top: 20, left: 20, right: 15),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[nricFrontBox, nricBackBox],
));
final buyerTypeWidget = Row(children: <Widget>[
Flexible(
child: ListTile(
onTap: () => setState(() {
buyerType = BuyerType.shop;
}),
title: Text(AppTranslations.of(context).text("reg.type_shop")),
leading: Radio(
activeColor: primaryColor,
value: BuyerType.shop,
groupValue: buyerType,
onChanged: (BuyerType value) {
setState(() {
buyerType = value;
});
},
),
),
),
Flexible(
child: ListTile(
onTap: () => setState(() {
buyerType = BuyerType.agent;
}),
title: Text(AppTranslations.of(context).text("reg.type_agent")),
leading: Radio(
activeColor: primaryColor,
value: BuyerType.agent,
groupValue: buyerType,
onChanged: (BuyerType value) {
setState(() {
buyerType = value;
});
},
),
),
),
]);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("reg.title")),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send),
onPressed: () {
showConfirmDialog(context, "reg.confirm", () {
_submit();
});
},
)
],
),
body: Container(
child: Form(
key: formKey,
child: ListView(
children: <Widget>[
companyName,
companyAddress,
buyerTypeWidget,
nric,
],
),
),
),
),
);
}
List<MyDataRow> getProductRow(Buyer buyer) {
return buyer.buyerProducts.map((b) {
return MyDataRow(
onSelectChanged: (bool selected) async {
final BuyerProduct buyerProduct = await Navigator.push<BuyerProduct>(
context,
MaterialPageRoute(
builder: (context) => MyRegisterationItem(
buyerProduct: b,
)),
);
_save(buyerProduct);
},
cells: [
MyDataCell(
new Text(
b.productName,
style: textStyle,
),
),
MyDataCell(
new Text(b.storageCapacityQty.toString(), style: textStyle),
),
MyDataCell(
new Text(b.dailySaleQty.toString(), style: textStyle),
),
],
);
}).toList();
}
String _validateBizName(value) {
if (value.isEmpty) {
return AppTranslations.of(context).text("reg.empty_biz_name");
}
return null;
}
String _validateBizAddress(value) {
if (value.isEmpty) {
return AppTranslations.of(context).text("reg.empty_biz_address");
}
return null;
}
String _validateShops(value) {
if (value.isEmpty) {
return AppTranslations.of(context).text("reg.empty_shops");
}
return null;
}
_save(BuyerProduct buyerProduct) {
if (buyerProduct == null) return;
if (buyerProduct.action == "create") {
if (buyer.buyerProducts.contains(buyerProduct)) {
showMsgDialog(context, "Error", "Duplicate line");
return;
}
buyer.buyerProducts.add(buyerProduct);
} else if (buyerProduct.action == "delete") {
buyer.buyerProducts.remove(buyerProduct);
}
}
_submit() async {
if (!formKey.currentState.validate()) {
return;
}
if (_isNew) {
if (attachments.nricFront == null || attachments.nricBack == null) {
showMsgDialog(
context, "Error", "Required NRIC front and back attachments");
return;
}
}
setState(() {
_isLoading = true;
});
try {
String type = buyerType == BuyerType.agent ? "agent" : "shop";
buyer.bizName = _bizName.text;
buyer.bizAddress = _bizAddress.text;
buyer.bizType = type;
if (_isNew) {
await Provider.of<RegModel>(context).register(buyer, attachments);
} else {
await Provider.of<RegModel>(context).update(buyer, attachments);
}
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,117 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/model/reg_model.dart';
import 'package:fcs/widget/label_widgets.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
import '../util.dart';
import '../widget/localization/app_translations.dart';
import 'my_registeration.dart';
enum BuyerType { shop, agent }
class MyRegisterationInfo extends StatefulWidget {
@override
_MyRegisterationInfoState createState() => _MyRegisterationInfoState();
}
class _MyRegisterationInfoState extends State<MyRegisterationInfo> {
bool inProgress = true;
BuyerType buyerType = BuyerType.shop;
TextEditingController _bizName = new TextEditingController();
TextEditingController _bizAddress = new TextEditingController();
TextEditingController _status = new TextEditingController();
@override
Widget build(BuildContext context) {
RegModel regModel = Provider.of<RegModel>(context);
MainModel mainModel = Provider.of<MainModel>(context);
buyerType =
regModel.reg.bizType == "shop" ? BuyerType.shop : BuyerType.agent;
_bizName.text = regModel.reg.bizName;
_bizAddress.text = regModel.reg.bizAddress;
_status.text = regModel.reg.status;
bool isRegBuyer = mainModel.isRegBuyer();
if (regModel.isLoaded) {
setState(() {
this.inProgress = false;
});
}
final bizNameBox =
labeledText(context, regModel.reg.bizName, "reg.biz_name");
final bizAddressBox =
labeledText(context, regModel.reg.bizAddress, "reg.biz_address");
final typeBox =
labeledText(context, regModel.reg.bizType, "buyer.type_biz");
final statusBox = labeledText(context, regModel.reg.status, "reg.status");
final dailyQuotaBox = labeledText(
context, formatNumber(regModel.reg.dailyQuota), "reg.quota",number: true);
final dailyQuotaUsedBox = labeledText(
context, formatNumber(regModel.reg.dailyQuotaUsed), "reg.quota.used",number: true);
final maxQuotaBox = labeledText(
context, formatNumber(regModel.reg.maxQuota), "reg.max_quota",number: true);
final maxQuotaUsedBox = labeledText(
context, formatNumber(regModel.reg.maxQuotaUsed), "reg.max_quota.used",number: true);
final nricFrontBox =
labeledImg(context, regModel.reg.nricFrontUrl, "reg_info.nric_front");
final nricBackBox =
labeledImg(context, regModel.reg.nricBackUrl, "reg_info.nric_back");
return LocalProgress(
inAsyncCall: inProgress,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("reg_info.title")),
actions: <Widget>[
!isRegBuyer
? Container()
: regModel.reg.isApproved()
? Container()
: IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => MyRegisteration(
buyer: regModel.reg,
)));
},
),
],
),
body: Container(
padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10),
child: ListView(
children: <Widget>[
bizNameBox,
Divider(),
bizAddressBox,
Divider(),
typeBox,
Divider(),
statusBox,
Divider(),
dailyQuotaBox,
Divider(),
dailyQuotaUsedBox,
Divider(),
maxQuotaBox,
Divider(),
maxQuotaUsedBox,
Divider(),
nricFrontBox,
Divider(),
nricBackBox,
Divider()
],
),
),
),
);
}
}

View File

@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/product_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/buyer.dart';
import 'package:fcs/vo/product.dart';
import 'package:fcs/widget/progress.dart';
class MyRegisterationItem extends StatefulWidget {
final BuyerProduct buyerProduct;
const MyRegisterationItem({Key key, this.buyerProduct}) : super(key: key);
@override
_MyRegisterationItemState createState() => _MyRegisterationItemState();
}
class _MyRegisterationItemState extends State<MyRegisterationItem> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
TextEditingController _storage = new TextEditingController();
TextEditingController _sales = new TextEditingController();
String currentProductID;
BuyerProduct buyerProduct = BuyerProduct(action: "create");
@override
void initState() {
super.initState();
if (widget.buyerProduct != null) {
buyerProduct = widget.buyerProduct;
buyerProduct.action = "update";
currentProductID = buyerProduct.productID;
_sales.text = buyerProduct.dailySaleQty.toString();
_storage.text = buyerProduct.storageCapacityQty.toString();
}
}
Widget showProducts(BuildContext context, ProductModel productModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(
FontAwesomeIcons.tag,
color: primaryColor,
size: 20,
),
SizedBox(
width: 20,
),
new Flexible(
child: Container(
width: 170.0,
child: DropdownButton<String>(
value: currentProductID,
isExpanded: true,
hint: Text(
'Select Product',
style: labelStyle,
),
onChanged: changedProduct,
items: productModel.products
.map<DropdownMenuItem<String>>((Product product) {
return new DropdownMenuItem<String>(
value: product.id,
child: new Text(product.name, style: textStyle),
);
}).toList(),
),
),
),
],
);
}
void changedProduct(selected) {
setState(() {
currentProductID = selected;
});
}
@override
Widget build(BuildContext context) {
ProductModel productModel = Provider.of<ProductModel>(context);
final volumeBox = Container(
padding: EdgeInsets.only(top: 10),
child: TextFormField(
controller: _storage,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
labelText: "Enter total storage capacity",
labelStyle: labelStyle,
icon: Image.asset(
"assets/volume.png",
width: 23,
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 total storage capacity";
}
return null;
},
),
);
final amountBox = Container(
child: TextFormField(
controller: _sales,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
labelText: "Enter daily sale quantity",
labelStyle: labelStyle,
icon: Image.asset(
"assets/sales.png",
width: 23,
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 daily sale quantity";
}
return null;
},
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text("Product registration"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
buyerProduct.action = "delete";
Navigator.pop(context, buyerProduct);
},
),
IconButton(
icon: Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState.validate()) return;
_save();
},
)
],
),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
showProducts(context, productModel),
volumeBox,
amountBox,
],
),
),
],
),
)),
);
}
_save() {
if (currentProductID == null) return;
this.buyerProduct.productID = currentProductID;
var productName =
Provider.of<ProductModel>(context).getProductName(currentProductID);
this.buyerProduct.productName = productName;
this.buyerProduct.storageCapacityQty = int.parse(_storage.text);
this.buyerProduct.dailySaleQty = int.parse(_sales.text);
Navigator.pop<BuyerProduct>(context, this.buyerProduct);
}
}

View File

@@ -0,0 +1,205 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/notification_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/notification.dart' as Noti;
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 '../theme/theme.dart';
class NotificationList extends StatefulWidget {
@override
_NotificationListState createState() => _NotificationListState();
}
class _NotificationListState extends State<NotificationList> {
var timeFormatter = new DateFormat('KK:mm a');
var dateFormatter = new DateFormat('dd MMM');
final double dotSize = 15.0;
int _selectedIndex = 0;
bool _isLoading = false;
bool _isClicked = false;
@override
Widget build(BuildContext context) {
NotificationModel notificationModel =
Provider.of<NotificationModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("noti.title")),
actions: <Widget>[
PopupMenuButton<PopupMenu>(
elevation: 3.2,
onSelected: (selected) {
setState(() {
this._selectedIndex = selected.index;
});
notificationModel.filter(selected.index);
},
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 notificationMenu.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.separated(
separatorBuilder: (context, index) => Divider(
color: Colors.black,
),
scrollDirection: Axis.vertical,
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
shrinkWrap: true,
itemCount: notificationModel.notis.length,
itemBuilder: (BuildContext context, int index) {
Noti.Notification noti = notificationModel.notis[index];
return Stack(
children: <Widget>[
InkWell(
onTap: () => _display(noti),
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: Icon(Icons.message),
),
new Expanded(
child: new Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
new Text(
noti.getDesc,
style: new TextStyle(
fontSize: 15.0,
color: Colors.black),
),
new Text(
noti.itemType == "reg"
? ""
: noti.itemNumber,
style: new TextStyle(
fontSize: 13.0, color: Colors.grey),
),
],
),
),
],
),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(timeFormatter.format(noti.time)),
),
noti.fromToday()
? Container()
: Text(dateFormatter.format(noti.time)),
],
)
],
),
),
noti.seen
? Container()
: new Positioned(
left: 11,
top: 11,
child: new Container(
padding: EdgeInsets.all(2),
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: BoxConstraints(
minWidth: 18,
minHeight: 18,
),
child: Text(
'new',
style: TextStyle(
color: Colors.white,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
)
],
);
}),
),
);
}
_display(Noti.Notification noti) async {
if (_isClicked) return;
_isClicked = true;
await displayNotiContent(context, noti);
_isClicked = false;
}
}

33
lib/pages/offline.dart Normal file
View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:fcs/widget/local_text.dart';
class Offline extends StatefulWidget {
@override
_OfflineState createState() => _OfflineState();
}
class _OfflineState extends State<Offline> {
@override
Widget build(BuildContext context) {
final retryButton = Card(
elevation: 23,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.offline_bolt),
LocalText(context, "offline.status"),
],
),
),
);
return Scaffold(
body: Center(child: retryButton),
);
}
}

227
lib/pages/pd/pd_form.dart Normal file
View File

@@ -0,0 +1,227 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.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/pd_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/pd.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/progress.dart';
import '../util.dart';
import 'pd_item.dart';
class PDForm extends StatefulWidget {
final PD pd;
const PDForm({Key key, this.pd}) : super(key: key);
@override
_PDFormState createState() => _PDFormState();
}
class _PDFormState extends State<PDForm> {
TextEditingController _date = new TextEditingController();
TextEditingController _userName = new TextEditingController();
PD pd = PD();
bool _isLoading = false;
bool isNew = true;
@override
void initState() {
super.initState();
if (widget.pd != null) {
_userName.text = widget.pd.userName;
} else {
var mainModel = Provider.of<MainModel>(context, listen: false);
_userName.text = mainModel.user.name;
}
_load();
}
_load() async {
if (widget.pd != null) {
this.pd = widget.pd;
_date.text = DateFormat('dd MMM yyyy hh:mm a').format(widget.pd.date);
isNew = false;
Provider.of<PDModel>(context, listen: false).loadPDLines(pd).then((_pd) {
setState(() {
this.pd = _pd;
});
});
}
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
final _formKey = GlobalKey<FormState>();
final dateBox = Container(
padding: EdgeInsets.only(left: 20, right: 15),
child: TextFormField(
controller: _date,
enabled: false,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("pd.date"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
Icons.date_range,
color: primaryColor,
size: 23,
)),
));
final nameBox = Container(
padding: EdgeInsets.only(left: 20, right: 15),
child: TextFormField(
controller: _userName,
autofocus: false,
readOnly: true,
style: textStyle,
decoration: new InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
icon: Icon(
Icons.person,
color: primaryColor,
size: 25,
)),
));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(
AppTranslations.of(context).text('pd'),
style: languageModel.isEng
? TextStyle()
: TextStyle(fontFamily: 'MyanmarUnicode'),
),
actions: <Widget>[
isNew
? IconButton(
icon: Icon(Icons.send),
onPressed: () {
if (!_formKey.currentState.validate()) return;
showConfirmDialog(context, "pd.confirm", () {
_submit();
});
},
)
: Container()
],
),
floatingActionButton: isNew
? FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: () async {
final PDLine pdLine = await Navigator.push<PDLine>(
context,
MaterialPageRoute(builder: (context) => PDItem()),
);
_save(pdLine);
},
)
: null,
body: Form(
key: _formKey,
child: ListView(
children: <Widget>[
isNew ? Container() : dateBox,
nameBox,
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
columns: [
MyDataColumn(
label: LocalText(context, "pd.product")),
MyDataColumn(
label: LocalText(context, "pd.storage"),
),
MyDataColumn(
label: LocalText(context, "pd.quantity")),
],
rows: getProductRow(pd),
),
),
],
),
)),
);
}
List<MyDataRow> getProductRow(PD pd) {
return pd.pdLines.map((p) {
return MyDataRow(
onSelectChanged: (bool selected) async {
if (!isNew) return;
var pdLine = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PDItem(
pdLine: p,
)),
);
_save(pdLine);
},
cells: [
MyDataCell(
new Text(
p.productName,
style: textStyle,
),
),
MyDataCell(
new Text(p.storageName, style: textStyle),
),
MyDataCell(
new Text(p.quantity.toString(), style: textStyle),
),
],
);
}).toList();
}
_save(PDLine pdLine) {
if (pdLine == null) return;
if (pdLine.action == "create") {
if (pd.pdLines.contains(pdLine)) {
showMsgDialog(context, "Error", "Duplicate line");
return;
}
pd.pdLines.add(pdLine);
} else if (pdLine.action == "delete") {
pd.pdLines.remove(pdLine);
}
}
_submit() async {
if (pd.pdLines.length == 0) {
showMsgDialog(context, "Error", "No product line");
return;
}
setState(() {
_isLoading = true;
});
try {
PDModel pdModel = Provider.of<PDModel>(context);
await pdModel.createPD(pd);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

223
lib/pages/pd/pd_item.dart Normal file
View File

@@ -0,0 +1,223 @@
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/product_model.dart';
import 'package:fcs/model/storage_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/pd.dart';
import 'package:fcs/vo/product.dart';
import 'package:fcs/vo/storage.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
class PDItem extends StatefulWidget {
final PDLine pdLine;
const PDItem({Key key, this.pdLine}) : super(key: key);
@override
_PDItemState createState() => _PDItemState();
}
class _PDItemState extends State<PDItem> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
String currentStorageID;
String currentProductID;
TextEditingController _quantity = new TextEditingController();
PDLine pdLine = PDLine();
@override
void initState() {
super.initState();
if (widget.pdLine != null) {
this._quantity.text = widget.pdLine.quantity.toString();
this.currentProductID = widget.pdLine.productID;
this.currentStorageID = widget.pdLine.storageID;
this.pdLine = widget.pdLine;
this.pdLine.action = "update";
} else {
this.pdLine.action = "create";
}
}
Widget showInventoryList(BuildContext context, StorageModel storageModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Image.asset(
"assets/inventory.png",
color: primaryColor,
width: 25,
),
SizedBox(
width: 20,
),
new Flexible(
child: Container(
width: 170.0,
child: DropdownButton<String>(
value: currentStorageID,
isExpanded: true,
hint: Text(
'Select Storage',
style: labelStyle,
),
onChanged: changedDropDownItem,
items: storageModel.storages
.map<DropdownMenuItem<String>>((Storage storage) {
return new DropdownMenuItem<String>(
value: storage.id,
child: new Text(storage.name, style: textStyle),
);
}).toList(),
),
),
),
],
);
}
void changedDropDownItem(selected) {
setState(() {
currentStorageID = selected;
});
}
Widget showProducts(BuildContext context, ProductModel productModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(
FontAwesomeIcons.tag,
color: primaryColor,
size: 20,
),
SizedBox(
width: 20,
),
new Flexible(
child: Container(
width: 170.0,
child: DropdownButton<String>(
value: currentProductID,
isExpanded: true,
hint: Text(
'Select Product',
style: labelStyle,
),
onChanged: changedProduct,
items: productModel.products
.map<DropdownMenuItem<String>>((Product product) {
return new DropdownMenuItem<String>(
value: product.id,
child: new Text(product.name, style: textStyle),
);
}).toList(),
),
),
),
],
);
}
void changedProduct(selected) {
setState(() {
currentProductID = selected;
});
}
@override
Widget build(BuildContext context) {
var storageModel = Provider.of<StorageModel>(context);
var productModel = Provider.of<ProductModel>(context);
var languageModel = Provider.of<LanguageModel>(context);
final quantityBox = Container(
padding: EdgeInsets.only(top: 10),
child: TextFormField(
controller: _quantity,
keyboardType: TextInputType.number,
autofocus: false,
cursorColor: primaryColor,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("pd.quantity"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUpAlt,
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("pd.form.quan");
}
return null;
},
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(
AppTranslations.of(context).text('pd.product.title'),
style: languageModel.isEng
? TextStyle()
: TextStyle(fontFamily: 'MyanmarUnicode'),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_delete();
},
),
IconButton(
icon: Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState.validate()) return;
_save();
},
)
],
),
body: Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 20.0),
children: <Widget>[
quantityBox,
showInventoryList(context, storageModel),
showProducts(context, productModel)
],
),
)),
);
}
_save() {
if (currentProductID == null || currentStorageID == null) return;
this.pdLine.storageID = currentStorageID;
var storageName =
Provider.of<StorageModel>(context).getStorageName(currentStorageID);
this.pdLine.storageName = storageName;
this.pdLine.productID = currentProductID;
var productName =
Provider.of<ProductModel>(context).getProductName(currentProductID);
this.pdLine.productName = productName;
this.pdLine.quantity = int.parse(_quantity.text);
Navigator.pop<PDLine>(context, this.pdLine);
}
_delete() {
this.pdLine.action = "delete";
Navigator.pop<PDLine>(context, this.pdLine);
}
}

187
lib/pages/pd/pd_list.dart Normal file
View File

@@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/pd_model.dart';
import 'package:fcs/pages/pd/pd_form.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
class PDList extends StatefulWidget {
@override
_PDListState createState() => _PDListState();
}
class _PDListState extends State<PDList> {
final double dotSize = 15.0;
DateTime _selectedDate = DateTime.now();
int _dateIndex = 0;
bool _isLoading = false;
@override
void initState() {
super.initState();
var pdModel = Provider.of<PDModel>(context, listen: false);
// pdModel.loadPDs();
_selectedDate = pdModel.selectedDate;
_dateIndex = pdModel.dateIndex;
}
Future<Null> _selectDate(BuildContext context) async {
var pdModel = Provider.of<PDModel>(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;
pdModel.filterDate(_selectedDate, _dateIndex);
});
}
}
@override
Widget build(BuildContext context) {
var pdModel = Provider.of<PDModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(
AppTranslations.of(context).text('pd.title'),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle()
: TextStyle(fontFamily: 'MyanmarUnicode'),
),
actions: <Widget>[
InkWell(
child: Container(
padding: EdgeInsets.only(right: 15, 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),
),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PDForm()),
);
},
),
body: new ListView.builder(
padding: EdgeInsets.only(left: 10, right: 10, top: 15),
shrinkWrap: true,
itemCount: pdModel.pds.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PDForm(
pd: pdModel.pds[index],
)),
);
},
child: Card(
elevation: 10,
color: Colors.white,
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.all(10),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Image.asset(
"assets/pdo.png",
width: 30,
color: primaryColor,
)),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
pdModel.pds[index].date == null
? ""
: DateFormat('dd MMM yyyy')
.format(pdModel.pds[index].date),
style: textStyle),
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: new Text(
pdModel.pds[index].pdNumber == null
? ''
: pdModel.pds[index].pdNumber,
style: new TextStyle(
fontSize: 12.0, color: Colors.grey),
),
),
],
),
SizedBox(
height: 15,
)
],
),
),
);
}),
),
);
}
}

103
lib/pages/phone_input.dart Normal file
View File

@@ -0,0 +1,103 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
class PhoneEditor extends StatefulWidget {
@override
_PhoneEditorState createState() => _PhoneEditorState();
}
class _PhoneEditorState extends State<PhoneEditor> {
final _formKey = GlobalKey<FormState>();
TextEditingController _phone = new TextEditingController();
@override
void initState() {
super.initState();
_phone.text ='09';
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Center(
child: Text(
AppTranslations.of(context).text("contact.phone.title"),
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: _phone,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
cursorColor: primaryColor,
decoration: new InputDecoration(
fillColor: primaryColor,
icon: Icon(
Icons.phone,
color: primaryColor,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
),
validator: (value) {
if (value.isEmpty) {
return AppTranslations.of(context)
.text("contact.phone.empty");
}
return null;
},
))
],
),
),
actions: <Widget>[
FlatButton(
child: LocalText(
context,
'do.cancel',
color: secondaryColor,
),
onPressed: () {
_phone.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() {
try {
Navigator.pop<String>(context, _phone.text);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
}
}
}

View File

@@ -0,0 +1,102 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_verification_code_input/flutter_verification_code_input.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:quiver/async.dart';
class PinLoginDialog extends StatefulWidget {
@override
_PinLoginDialogState createState() => _PinLoginDialogState();
}
class _PinLoginDialogState extends State<PinLoginDialog> {
String pin;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
var mainModel = Provider.of<MainModel>(context);
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0))),
title: Column(
children: <Widget>[
Image.asset(
"assets/pin.png",
height: 90,
color: primaryColor,
),
Text(
"Enter PIN Code",
style: TextStyle(
color: primaryColor, fontWeight: FontWeight.bold, fontSize: 20),
),
],
),
content: Container(
width: double.maxFinite,
height: 120.0,
child: new ListView(
shrinkWrap: true,
children: <Widget>[
Center(
child: VerificationCodeInput(
keyboardType: TextInputType.number,
length: 6,
autofocus: false,
itemSize: 40,
itemDecoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
),
),
textStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 23),
onCompleted: (String value) {
this.pin = value;
},
),
),
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
}),
FlatButton(
color: primaryColor,
child: Text("OK",
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold)),
onPressed: () async {
if (this.pin == null) return;
if (mainModel.user.pin == this.pin) {
mainModel.resetPinTimer();
Navigator.of(context).pop();
} else {
showMsgDialog(context, "Error", "Invalid PIN Code !");
}
}),
],
)
],
),
),
);
}
}

View File

@@ -0,0 +1,27 @@
import 'dart:io';
class POFiles {
File poPaymentFile, storageChargeFile;
List<File> poPaymentFilesAdded=[];
List<String> poPaymentFilesRemoved=[]; // only url
bool poFileChanged = false, storageFileChanged = false;
set addPoPaymentFile(File file) {
poPaymentFilesAdded.add(file);
poFileChanged = true;
}
set removePoPaymentFile(String url) {
poPaymentFilesRemoved.add(url);
poFileChanged = true;
}
set setStorageChargeFile(File file) {
storageChargeFile = file;
storageFileChanged = true;
}
bool get anyChanged => poFileChanged || storageFileChanged;
}

208
lib/pages/po/po_item.dart Normal file
View File

@@ -0,0 +1,208 @@
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/product_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/po.dart';
import 'package:fcs/vo/product.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 POItem extends StatefulWidget {
final POLine poLine;
const POItem({Key key, this.poLine}) : super(key: key);
@override
_POItemState createState() => _POItemState();
}
class _POItemState extends State<POItem> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
String currentProductID;
TextEditingController _qty = new TextEditingController();
POLine poLine = POLine();
@override
void initState() {
super.initState();
if (widget.poLine != null) {
this.poLine = widget.poLine;
this._qty.text = this.poLine.qty.toString();
this.currentProductID = this.poLine.productID;
this.poLine.action = "update";
} else {
this.poLine.action = "create";
}
}
@override
void dispose() {
super.dispose();
}
Widget showProducts(BuildContext context, ProductModel productModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(
FontAwesomeIcons.tag,
color: primaryColor,
size: 20,
),
SizedBox(
width: 20,
),
new Flexible(
child: Container(
width: 170.0,
child: DropdownButton<String>(
value: currentProductID,
isExpanded: true,
hint: Text(
'Select Product',
style: labelStyle,
),
onChanged: changedProduct,
items: productModel.products
.map<DropdownMenuItem<String>>((Product product) {
return new DropdownMenuItem<String>(
value: product.id,
child: new Text(product.name, style: textStyle),
);
}).toList(),
),
),
),
],
);
}
void changedProduct(selected) {
setState(() {
// currentProductID = selected;
});
}
@override
Widget build(BuildContext context) {
var productModel = Provider.of<ProductModel>(context);
var languageModel = Provider.of<LanguageModel>(context);
final volumeBox = Container(
padding: EdgeInsets.only(top: 10),
child: TextFormField(
controller: _qty,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("po.volume"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUpAlt,
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("po.form.volume");
}
return null;
},
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
"po",
color: Colors.white,
fontSize: 20,
),
actions: <Widget>[
// IconButton(
// icon: Icon(Icons.delete),
// onPressed: () {
// _delete();
// },
// ),
IconButton(
icon: Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState.validate()) return;
_save();
},
)
],
),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
volumeBox,
showProducts(context, productModel)
],
),
),
],
),
)),
);
}
_save() {
setState(() {
_isLoading = true;
});
try {
this.poLine.productID = currentProductID;
var product =
Provider.of<ProductModel>(context).getProduct(currentProductID);
this.poLine.productName = product.name;
this.poLine.price = product.price;
this.poLine.qty = int.parse(_qty.text);
this.poLine.amount = this.poLine.price * this.poLine.qty;
Navigator.pop<POLine>(context, this.poLine);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_delete() {
setState(() {
_isLoading = true;
});
try {
this.poLine.action = "delete";
Navigator.pop<POLine>(context, this.poLine);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,601 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.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/po/po_item.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/attach.dart';
import 'package:fcs/vo/po.dart';
import 'package:fcs/widget/img_file.dart';
import 'package:fcs/widget/label_widgets.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/multi_img_controller.dart';
import 'package:fcs/widget/multi_img_file.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';
import '../document_log_page.dart';
import '../util.dart';
import 'po_files.dart';
class POSubmissionForm extends StatefulWidget {
final POSubmission poSubmission;
POSubmissionForm({this.poSubmission});
@override
_POSubmissionFormState createState() => _POSubmissionFormState();
}
class _POSubmissionFormState extends State<POSubmissionForm> {
final numberFormatter = new NumberFormat("#,###");
var dateFormatter = new DateFormat('dd MMM yyyy - hh:mm a');
MultiImgController multiImgController = MultiImgController();
TextEditingController _numberController = new TextEditingController();
TextEditingController _storage = new TextEditingController();
TextEditingController _comment = new TextEditingController();
TextEditingController _name = new TextEditingController();
TextEditingController _bizName = new TextEditingController();
List<POLine> poLines = new List();
bool _isLoading = false;
bool _isNew = true;
int _amount = 0;
String _date = "", _status = "";
POSubmission poSubmission = POSubmission();
AttachFile attachFile;
POFiles files = POFiles();
@override
void initState() {
super.initState();
if (widget.poSubmission != null) {
_isNew = false;
poSubmission = widget.poSubmission;
_date = dateFormatter.format(poSubmission.poDate);
_numberController.text = poSubmission.poNumber.toString();
_comment.text = poSubmission.comment;
_name.text = poSubmission.userName;
_bizName.text = poSubmission.bizName;
poLines = poSubmission.poLines;
_status = poSubmission.status;
_storage.text = poSubmission.storageCharge.toString();
multiImgController.setImageUrls = poSubmission.poReceiptUrls;
Provider.of<POSubmissionModel>(context, listen: false)
.loadPOLines(poSubmission.id)
.then((poLines) {
setState(() {
this.poSubmission.poLines = poLines;
_amount = poSubmission.getAmount;
});
});
} else {
_amount = 0;
_date = dateFormatter.format(DateTime.now());
var productModel = Provider.of<ProductModel>(context, listen: false);
productModel.products.forEach((p) {
var _poLine = POLine(
productID: p.id,
productName: p.name,
price: p.price,
balanceQty: 0,
qty: 0,
amount: 0);
poSubmission.poLines.add(_poLine);
});
}
}
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
bool isBuyer = mainModel.user.isBuyer();
var logModel = Provider.of<LogModel>(context);
final dateBox = Container(
padding: EdgeInsets.only(top: 15, left: 20, bottom: 5),
child: Row(
children: <Widget>[
LocalText(context, "po.date"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(_date, style: textStyle),
),
],
),
);
final numberBox = Container(
padding: EdgeInsets.only(top: 5, left: 20, bottom: 5),
child: Row(
children: <Widget>[
LocalText(context, "po.number"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_numberController.text,
style: textStyle,
),
)
],
),
);
final userNameBox = Container(
padding: EdgeInsets.only(top: 5, left: 20, bottom: 5),
child: Row(
children: <Widget>[
LocalText(context, "po.name"),
Container(
padding: EdgeInsets.only(left: 20),
child: Text(
_name.text,
style: textStyle,
),
)
],
),
);
final bizNameBox = Container(
padding: EdgeInsets.only(top: 5, left: 20, bottom: 5),
child: Row(
children: <Widget>[
LocalText(context, "po.biz"),
Container(
padding: EdgeInsets.only(left: 20),
child: Text(
_bizName.text,
style: textStyle,
),
)
],
),
);
final statusBox = Container(
padding: EdgeInsets.only(top: 5, left: 20),
child: Row(
children: <Widget>[
LocalText(context, "po.status"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_status,
style: textStyle,
),
)
],
),
);
final commentBox = Container(
padding: EdgeInsets.only(top: 5, left: 20),
child: Row(
children: <Widget>[
LocalText(context, "po.comment"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_comment.text,
style: textStyle,
),
)
],
),
);
final amountBox = Container(
padding: EdgeInsets.only(top: 5, left: 10),
child: labeledText(context, formatNumber(_amount), "po.amount",
number: true));
final storageBox = Container(
padding: EdgeInsets.only(top: 5, left: 20),
child: Row(
children: <Widget>[
LocalText(context, "po.storage_charge"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_storage.text,
style: textStyle,
),
)
],
),
);
final poPaymentBox = Container(
padding: EdgeInsets.only(left: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocalText(context, "po.po_payment_receipt"),
MultiImageFile(
enabled: isBuyer
? _isNew || this.poSubmission.isPending() ? true : false
: false,
controller: multiImgController,
title: "Receipt File",
)
]));
final storagePaymentBox = Container(
padding: EdgeInsets.only(left: 20),
child: Row(children: <Widget>[
LocalText(context, "po.storage_receipt"),
ImageFile(
enabled: isBuyer
? _isNew || this.poSubmission.isPending() ? true : false
: false,
initialImgUrl: this.poSubmission.storageReceiptUrl,
title: "Receipt File",
onFile: (file) {
this.files.setStorageChargeFile = file;
}),
]));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("po"),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle(fontSize: 18)
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
actions: <Widget>[
_isNew || !mainModel.showHistoryBtn()
? Container()
: IconButton(
icon: Icon(Icons.history),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DocumentLogPage(docID: poSubmission.id)),
);
},
),
isBuyer && (_isNew || poSubmission.isPending())
? IconButton(
icon: Icon(Icons.send),
onPressed: () {
showConfirmDialog(context, "po.confirm", () {
_submit();
});
},
)
: Container(),
isBuyer
? Container()
: PopupMenuButton(
onSelected: _select,
itemBuilder: (context) => [
PopupMenuItem(
enabled: poSubmission.isPending(),
value: 1,
child: Text("Approve PO"),
),
PopupMenuItem(
enabled: poSubmission.isPending(),
value: 2,
child: Text("Reject PO"),
),
PopupMenuItem(
enabled: mainModel.user.isOwner() &&
poSubmission.isApproved(),
value: 3,
child: Text("Cancel PO"),
),
],
),
],
),
// floatingActionButton: isBuyer && (_isNew || poSubmission.isPending())
// ? FloatingActionButton(
// backgroundColor: primaryColor,
// child: Icon(Icons.add),
// onPressed: () async {
// final POLine poLine = await Navigator.push<POLine>(
// context,
// MaterialPageRoute(builder: (context) => POItem()),
// );
// _save(poLine);
// },
// )
// : null,
body: Container(
child: ListView(
children: <Widget>[
dateBox,
Divider(),
_isNew ? Container() : numberBox,
_isNew ? Container() : Divider(),
_isNew ? Container() : userNameBox,
_isNew ? Container() : Divider(),
_isNew ? Container() : bizNameBox,
_isNew ? Container() : Divider(),
_isNew ? Container() : statusBox,
_isNew ||
widget.poSubmission.comment == null ||
widget.poSubmission.comment == ''
? Container()
: Divider(),
_isNew ||
widget.poSubmission.comment == null ||
widget.poSubmission.comment == ''
? Container()
: commentBox,
_isNew ? Container() : Divider(),
amountBox,
Divider(),
poPaymentBox,
Divider(),
_isNew || !poSubmission.hasStorageCharge()
? Container()
: storageBox,
_isNew || !poSubmission.hasStorageCharge()
? Container()
: Divider(),
_isNew || !poSubmission.hasStorageCharge()
? Container()
: storagePaymentBox,
_isNew || !poSubmission.hasStorageCharge()
? Container()
: Divider(),
Container(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
headingRowHeight: 40,
columnSpacing: 7,
columns: _isNew
? [
MyDataColumn(
label: LocalText(context, "po.product")),
MyDataColumn(
label: LocalText(context, "po.price"),
numeric: true),
MyDataColumn(
label: LocalText(context, "po.volume"),
numeric: true),
MyDataColumn(
label: LocalText(context, "po.amount"),
numeric: true),
]
: [
MyDataColumn(
label: LocalText(context, "po.product")),
MyDataColumn(
label: LocalText(context, "po.price"),
numeric: true),
MyDataColumn(
label: LocalText(context, "po.balance.volume"),
numeric: true),
MyDataColumn(
label: LocalText(context, "po.volume"),
numeric: true),
MyDataColumn(
label: LocalText(context, "po.amount"),
numeric: true),
],
rows: getProductRow(poSubmission.poLines),
),
),
),
],
),
),
),
);
}
_select(s) {
if (s == 1) {
showConfirmDialog(context, "po.approve.confirm", () {
_approve();
});
} else if (s == 2) {
showCommentDialog(context, (comment) {
this.poSubmission.comment = comment;
_reject();
});
} else if (s == 3) {
showConfirmDialog(context, "po.cancel.confirm", () {
_cancel();
});
}
}
List<MyDataRow> getProductRow(List<POLine> poLines) {
MainModel mainModel = Provider.of<MainModel>(context);
bool isBuyer = mainModel.user.isBuyer();
ProductModel productModel = Provider.of<ProductModel>(context);
if (poLines.isNotEmpty) {
poLines.forEach((d) {
productModel.products.forEach((p) {
if (p.id == d.productID) {
d.displayOrder = p.displayOrder;
} else {
return;
}
});
});
poLines.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
}
return poLines.map((p) {
return MyDataRow(
onSelectChanged: (bool selected) async {
if (!isBuyer) return;
if (_isNew || this.poSubmission.isPending()) {
var poLine = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => POItem(
poLine: p,
)),
);
_save(poLine);
}
},
cells: _isNew
? [
MyDataCell(
new Text(
p.productName,
style: textStyle,
),
),
MyDataCell(NumberCell(p.price)),
MyDataCell(
Container(
alignment: Alignment.centerRight,
width: 100,
child: NumberCell(
p.qty,
textStyle: textStyleOdd,
),
),
),
MyDataCell(NumberCell(p.amount)),
]
: [
MyDataCell(
new Text(
p.productName,
style: textStyle,
),
),
MyDataCell(NumberCell(p.price)),
MyDataCell(NumberCell(p.balanceQty)),
MyDataCell(NumberCell(p.qty)),
MyDataCell(NumberCell(p.amount)),
],
);
}).toList();
}
_save(POLine poLine) {
if (poLine == null) return;
if (poLine.action == "create") {
if (poSubmission.poLines.contains(poLine)) {
showMsgDialog(context, "Error", "Duplicate line");
return;
}
poSubmission.poLines.add(poLine);
} else if (poLine.action == "delete") {
poSubmission.poLines.remove(poLine);
}
setState(() {
_amount = poSubmission.getAmount;
});
}
_submit() async {
if (poSubmission.poLines.length == 0) {
showMsgDialog(context, "Error", "No product line");
return;
}
List<POLine> _poLines = [];
poSubmission.poLines.forEach((p) => p.qty <= 0 ? _poLines.add(p) : p);
poSubmission.poLines.removeWhere((p) => p.qty <= 0);
setState(() {
_isLoading = true;
});
try {
POSubmissionModel poModel = Provider.of<POSubmissionModel>(context);
if (_isNew) {
await poModel.createPO(poSubmission, multiImgController.getAddedFile);
} else {
if (poSubmission.hasStorageCharge()) {
if (files.storageChargeFile == null) {
showMsgDialog(
context, "Error", "Please insert storage charge file");
return;
}
}
await poModel.updatePO(poSubmission, multiImgController.getAddedFile,
multiImgController.getDeletedUrl);
}
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
_poLines.forEach((e) {
if (!poSubmission.poLines.contains(e)) poSubmission.poLines.add(e);
});
});
}
}
_approve() async {
setState(() {
_isLoading = true;
});
var oldStatus = poSubmission.status;
try {
POSubmissionModel poModel = Provider.of<POSubmissionModel>(context);
poSubmission.status = "approved";
await poModel.approvePO(poSubmission);
Navigator.pop(context);
} catch (e) {
poSubmission.status = oldStatus;
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_reject() async {
setState(() {
_isLoading = true;
});
try {
POSubmissionModel poModel = Provider.of<POSubmissionModel>(context);
await poModel.rejectPO(poSubmission);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_cancel() async {
setState(() {
_isLoading = true;
});
try {
POSubmissionModel poModel = Provider.of<POSubmissionModel>(context);
await poModel.cancelPO(poSubmission);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,291 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.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/pages/po/po_submission_form.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/po.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 SubmissionList extends StatefulWidget {
@override
_SubmissionListState createState() => _SubmissionListState();
}
class _SubmissionListState extends State<SubmissionList> {
var dateFormatter = new DateFormat('dd MMM yyyy');
final double dotSize = 10.0;
PopupMenu selectedChoices = poMenus[0];
POSubmission poSubmission;
DateTime _selectedDate = DateTime.now();
String status;
int _selectedIndex = 0;
int _dateIndex = 0;
bool _isLoading = false;
@override
void initState() {
super.initState();
var poModel = Provider.of<POSubmissionModel>(context, listen: false);
_selectedIndex = poModel.popupMenu.index;
_dateIndex = poModel.dateIndex;
_selectedDate = poModel.selectedDate;
}
@override
void dispose() {
super.dispose();
}
Future<Null> _selectDate(BuildContext context) async {
var poSubmissionModel = Provider.of<POSubmissionModel>(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;
poSubmissionModel.filterData(
this.status, _selectedDate, this._selectedIndex, this._dateIndex);
});
}
}
@override
Widget build(BuildContext context) {
var poSubmissionModel = Provider.of<POSubmissionModel>(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("po.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;
}
poSubmissionModel.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: isBuyer
? FloatingActionButton(
backgroundColor: primaryColor,
heroTag: "btn2",
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => POSubmissionForm()),
),
child: Icon(Icons.add),
)
: null,
body: new ListView.builder(
scrollDirection: Axis.vertical,
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
shrinkWrap: true,
itemCount: poSubmissionModel.pos.length,
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 10,
color: Colors.white,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => POSubmissionForm(
poSubmission: poSubmissionModel.pos[index],
)),
);
},
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: 15.0 - dotSize / 2),
child: Padding(
padding: EdgeInsets.all(5.0),
child: Image.asset(
"assets/pay.png",
width: 40,
height: 40,
color: primaryColor,
),
),
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
poSubmissionModel.pos[index].poNumber
.toString(),
style: new TextStyle(
fontSize: 12.0, color: Colors.black),
),
new Text(
dateFormatter.format(
poSubmissionModel.pos[index].poDate),
style: new TextStyle(
fontSize: 12.0, color: Colors.grey),
),
!isBuyer
? new Text(
poSubmissionModel
.pos[index].userName
.toString(),
style: new TextStyle(
fontSize: 12.0,
color: Colors.grey),
)
: Container()
],
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.only(right: 18.0),
child: getStatus(poSubmissionModel.pos[index].status),
),
],
),
),
);
}),
),
);
}
}

View File

@@ -0,0 +1,223 @@
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/product_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/product.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
class ProductEditItem extends StatefulWidget {
final Product product;
const ProductEditItem({Key key, this.product}) : super(key: key);
@override
_ProductEditItemState createState() => _ProductEditItemState();
}
class _ProductEditItemState extends State<ProductEditItem> {
TextEditingController nameController = new TextEditingController();
TextEditingController priceController = new TextEditingController();
TextEditingController orderController = new TextEditingController();
int color = primaryColor.value;
bool isDisable = false;
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
@override
void initState() {
super.initState();
if (widget.product != null) {
this.color = widget.product.color;
nameController.text = widget.product.name;
priceController.text = widget.product.price.toString();
orderController.text = widget.product.displayOrder.toString();
if (widget.product.isDisable != null) {
isDisable = widget.product.isDisable;
} else {
isDisable = false;
}
}
}
@override
Widget build(BuildContext context) {
var maingModel = Provider.of<LanguageModel>(context);
final nameWidget = Container(
padding: EdgeInsets.only(top: 10),
child: TextFormField(
controller: nameController,
autofocus: true,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
icon: InkWell(
child: Icon(
FontAwesomeIcons.tag,
color: Color(this.color),
size: 25,
),
onTap: () => showColorPicker(context, Color(this.color), (color) {
setState(() {
this.color = color.value;
});
}),
),
labelText: AppTranslations.of(context).text("product.name"),
labelStyle: maingModel.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("product.name_empty");
}
return null;
},
),
);
final priceInput = Container(
child: TextFormField(
controller: priceController,
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
keyboardType: TextInputType.number,
cursorColor: primaryColor,
decoration: new InputDecoration(
icon: Icon(
FontAwesomeIcons.moneyBill,
color: primaryColor,
size: 25,
),
border: InputBorder.none,
labelText: AppTranslations.of(context).text("product.new_price"),
labelStyle: maingModel.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("product.price_empty");
}
return null;
},
),
);
final orderInput = Container(
child: TextFormField(
controller: orderController,
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
keyboardType: TextInputType.number,
cursorColor: primaryColor,
decoration: new InputDecoration(
icon: Icon(
Icons.sort,
color: primaryColor,
size: 25,
),
border: InputBorder.none,
labelText: AppTranslations.of(context).text("product.order"),
labelStyle: maingModel.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("product.order_empty");
}
return null;
},
),
);
final disableBox = Container(
padding: EdgeInsets.only(top: 10),
child: CheckboxListTile(
title: Text("Disable"),
value: isDisable,
activeColor: primaryColor,
onChanged: (value) {
setState(() {
isDisable = value;
});
},
controlAffinity: ListTileControlAffinity.leading,
));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: Text(AppTranslations.of(context).text("product.item"),
style: maingModel.isEng
? TextStyle(color: Colors.white, fontSize: 20.0)
: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontFamily: 'MyanmarUnicode')),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState.validate()) return;
Provider.of<ProductModel>(context, listen: false).saveProduct(
widget.product,
nameController.text,
priceController.text,
orderController.text,
color,
isDisable);
Product _product = new Product();
if (widget.product != null) {
_product = widget.product;
_product.name = nameController.text;
_product.price = int.parse(priceController.text);
_product.color = color;
_product.displayOrder = int.parse(orderController.text);
_product.isDisable = isDisable;
if (_product.id == null) {
_product.action = "create";
} else {
_product.action = "update";
}
}
Navigator.pop<Product>(context, _product);
},
)
],
backgroundColor: primaryColor,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.only(
left: 25.0,
right: 25.0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
nameWidget,
priceInput,
orderInput,
this.widget.product != null ? disableBox : Container(),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,68 @@
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/theme/theme.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/products.dart';
import 'package:fcs/widget/progress.dart';
import '../model/product_model.dart';
import 'products_list_edit.dart';
class ProductsList extends StatefulWidget {
@override
_ProductsListState createState() => _ProductsListState();
}
class _ProductsListState extends State<ProductsList> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
var productModel = Provider.of<ProductModel>(context);
var mainModel = Provider.of<MainModel>(context);
bool _isLoading = false;
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("products.title"),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle()
: TextStyle(fontFamily: 'MyanmarUnicode')),
actions: <Widget>[
mainModel.user != null && mainModel.user.isOwner() ||
mainModel.user.hasAdmin()
? IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductsListEdit(
products: productModel.productsToEdit,
)),
);
},
)
: Container()
],
),
backgroundColor: Colors.white,
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 5),
child: ProductsWidget(isWelcomePage: false),
),
],
)),
);
}
}

View File

@@ -0,0 +1,175 @@
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/product_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/product.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
import 'product_edit_item.dart';
class ProductsListEdit extends StatefulWidget {
final List<Product> products;
const ProductsListEdit({Key key, this.products}) : super(key: key);
@override
_ProductsListEditState createState() => _ProductsListEditState();
}
class _ProductsListEditState extends State<ProductsListEdit> {
List<Product> products;
bool _isLoading = false;
@override
void initState() {
super.initState();
products = widget.products;
}
@override
Widget build(BuildContext context) {
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("products.title"),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle(color: Colors.white, fontSize: 20.0)
: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontFamily: 'MyanmarUnicode')),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send),
onPressed: () {
_update(context);
},
)
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: _add,
),
body: ListView(
shrinkWrap: true,
padding: EdgeInsets.all(5.0),
children: <Widget>[
new ListView.builder(
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
shrinkWrap: true,
itemCount: products.length,
itemBuilder: (BuildContext context, int index) {
return _row(products[index]);
}),
],
),
),
);
}
_add() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProductEditItem()),
);
}
void _update(BuildContext context) async {
await showConfirmDialog(context, "product.confirm", () async {
setState(() {
_isLoading = true;
});
try {
var productModel = Provider.of<ProductModel>(context);
await productModel.updateProducts(this.products);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
});
}
Widget _row(Product product) {
return Container(
child: InkWell(
child: Card(
elevation: 10,
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0),
child: new Row(
children: <Widget>[
InkWell(
child: new Padding(
padding: new EdgeInsets.symmetric(
horizontal: 20.0 - 10 / 2),
child: Icon(
FontAwesomeIcons.tag,
color: Color(product.color),
size: 30,
),
),
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
product.name,
style: product.isDisable == true
? TextStyle(
decoration:
TextDecoration.lineThrough,
color: Colors.red)
: null,
)
],
),
),
],
),
),
),
Column(children: <Widget>[
product.id == null
? Text("New Product")
: Text("Old Price: ${product.oldPirce}"),
Text("New Price: ${product.price}")
]),
],
),
),
),
onTap: () async {
Product _p = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductEditItem(
product: product,
)),
);
if (_p == null) return;
setState(() {
product = _p;
});
}),
);
}
}

113
lib/pages/profile_edit.dart Normal file
View File

@@ -0,0 +1,113 @@
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/pages/util.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
typedef void ProfileCallback();
class ProfileEdit extends StatefulWidget {
@override
_ProfileEditState createState() => _ProfileEditState();
}
class _ProfileEditState extends State<ProfileEdit> {
final TextEditingController nameController = new TextEditingController();
bool _loading = false;
@override
void initState() {
super.initState();
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
nameController.text = mainModel.user.name;
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
final name = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 15, bottom: 30),
child: TextFormField(
controller: nameController,
autofocus: true,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("profile.name"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
Icons.account_box,
color: primaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
),
));
final saveBtn = Card(
elevation: 23,
child: ButtonTheme(
minWidth: 200.0,
height: 50.0,
child: FlatButton.icon(
onPressed: () => _save(),
label: Text(AppTranslations.of(context).text("btn.save"),
style: languageModel.isEng
? TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal)
: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode")),
icon: Icon(
Icons.save,
),
),
),
);
return LocalProgress(
inAsyncCall: _loading,
child: Scaffold(
appBar: AppBar(
title: Text(
AppTranslations.of(context).text("profile.edit_title"),
),
backgroundColor: primaryColor,
),
body: Column(
children: <Widget>[
name,
saveBtn,
],
),
),
);
}
_save() async {
setState(() {
_loading = true;
});
try {
await Provider.of<MainModel>(context, listen: false)
.updateProfile(nameController.text);
Navigator.pop(context);
setState(() {
_loading = false;
});
} catch (e) {
showMsgDialog(context, "Error", e.toString());
}
}
}

392
lib/pages/profile_page.dart Normal file
View File

@@ -0,0 +1,392 @@
import 'package:flutter/material.dart';
import 'package:package_info/package_info.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/profile_edit.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/role.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/localization/transalation.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
import 'profile_setting.dart';
typedef void ProfileCallback();
class Profile extends StatefulWidget {
@override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
bool _isLoading = false;
String selectedLanguage;
TextEditingController bizNameController = new TextEditingController();
static final List<String> languagesList = Translation().supportedLanguages;
static final List<String> languageCodesList =
Translation().supportedLanguagesCodes;
final Map<dynamic, dynamic> languagesMap = {
languagesList[0]: languageCodesList[0],
languagesList[1]: languageCodesList[1],
};
buildLanguage(LanguageModel languageModel) async {
var lan = await languageModel.load();
if (this.selectedLanguage != lan) {
setState(() {
this.selectedLanguage = lan;
});
}
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
MainModel mainModel = Provider.of<MainModel>(context);
buildLanguage(languageModel);
_selectedDropdown(String selected) {
setState(() {
selectedLanguage = selected;
languageModel.saveLanguage(selectedLanguage);
});
}
final namebox = Container(
padding: EdgeInsets.only(top: 10),
child: Container(
height: 45.0,
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 0.0),
child: Text(
AppTranslations.of(context).text("profile.name"),
style: languageModel.isEng
? TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal)
: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode"),
),
),
SizedBox(
width: 30,
),
Container(
child: Center(
child: Text(
mainModel.user == null ? "" : mainModel.user.name,
style:
TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
),
),
)
],
),
));
final phonenumberbox = Container(
height: 45.0,
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 0.0),
child: Text(
AppTranslations.of(context).text("profile.phone"),
style: languageModel.isEng
? TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal)
: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode"),
),
),
SizedBox(
width: 27,
),
Container(
child: Center(
child: Text(
mainModel.user == null
? ""
: mainModel.user.phone == null ? '' : mainModel.user.phone,
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
),
),
)
],
),
);
final emailBox = Container(
padding: EdgeInsets.only(top: 10, left: 0),
child: Row(
children: <Widget>[
Text(
AppTranslations.of(context).text("profile.email"),
style: languageModel.isEng
? TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal)
: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode"),
),
SizedBox(
width: 35,
),
Text(
mainModel.user == null
? ""
: mainModel.user.email == null || mainModel.user.email == ''
? ''
: mainModel.user.email,
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
),
],
),
);
final languageBox = Container(
padding: EdgeInsets.only(bottom: 15, top: 7),
child: Container(
height: 45.0,
child: Row(
children: <Widget>[
Text(
AppTranslations.of(context).text("profile.language"),
style: languageModel.isEng
? TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal)
: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode"),
),
Container(
width: 140,
padding: EdgeInsets.only(left: 30),
child: Theme(
data: new ThemeData(
canvasColor: Colors.white,
),
child: DropdownButton(
hint: Text("English"),
value: selectedLanguage,
isExpanded: true,
iconSize: 40,
items: languagesList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: _selectedDropdown),
)),
],
),
));
final logoutbutton = Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Card(
elevation: 23,
child: Container(
height: 45.0,
child: ButtonTheme(
minWidth: 900.0,
height: 100.0,
child: FlatButton.icon(
onPressed: () {
showConfirmDialog(context, "profile.logout.confirm",
() async {
setState(() {
_isLoading = true;
});
await mainModel.logout();
// Navigator.of(context)
// .pushNamedAndRemoveUntil("/", ModalRoute.withName('/'));
Future.delayed(Duration(seconds: 1), () {
if (mounted) {
setState(() {
_isLoading = false;
});
}
});
});
},
label: Text(AppTranslations.of(context).text("profile.logout"),
style: languageModel.isEng
? TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal)
: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode")),
icon: Icon(
Icons.exit_to_app,
),
),
),
),
));
Future<String> getVersionNumber() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version + "+" + packageInfo.buildNumber;
return version;
}
final versionbox = Container(
padding: EdgeInsets.only(top: 15),
child: Container(
child: Center(
child: FutureBuilder(
future: getVersionNumber(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) =>
Text(
snapshot.hasData ? "v${snapshot.data}" : "Loading ...",
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
),
)),
));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: Text(
AppTranslations.of(context).text("profile.title"),
),
backgroundColor: primaryColor,
actions: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfileEdit()),
);
},
),
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfileSetting()),
);
},
)
],
),
body: ListView(
padding: EdgeInsets.only(
left: 25.0,
right: 25.0,
),
shrinkWrap: true,
children: <Widget>[
namebox,
mainModel.isBuyer() ? Container() : getPrivilegeBox(context),
phonenumberbox,
mainModel.user == null
? Container()
: mainModel.user.email == null || mainModel.user.email == ''
? Container()
: emailBox,
languageBox,
logoutbutton,
Divider(color: secondaryColor),
versionbox,
SizedBox(
height: 20,
)
],
),
),
);
}
Widget getPrivilegeBox(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
var userModel = Provider.of<UserModel>(context);
return ListTileTheme(
contentPadding: EdgeInsets.all(0),
child: ExpansionTile(
title: Text(
AppTranslations.of(context).text("profile.privilege"),
style: languageModel.isEng
? TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
)
: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
fontFamily: "MyanmarUnicode"),
),
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:
getRowPrivilegeWidget(userModel.getUserPrivileges())),
),
)
],
),
);
}
List<Widget> getRowPrivilegeWidget(List<Privilege> privileges) {
return privileges.map((p) {
return Container(
padding: EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(p.name,
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal)),
SizedBox(
width: 30,
),
Container(
child: Text(
"- ${p.desc}",
style: TextStyle(fontSize: 16.0, fontStyle: FontStyle.normal),
),
)
],
),
);
}).toList();
}
}

View File

@@ -0,0 +1,201 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
import 'add_pin_editor.dart';
import 'block_list.dart';
import 'chage_phone_number.dart';
import 'change_password.dart';
import 'device_list.dart';
import 'email_page.dart';
import 'log_list.dart';
class ProfileSetting extends StatefulWidget {
@override
_ProfileSettingtate createState() => _ProfileSettingtate();
}
class _ProfileSettingtate extends State<ProfileSetting> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: LocalText(
context,
"setting.title",
fontSize: 20,
color: Colors.white,
),
backgroundColor: primaryColor,
),
body: SingleChildScrollView(
padding: EdgeInsets.only(
left: 25.0,
right: 25.0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 8,
),
buildSettingTile(
context: context,
text: 'log.title',
image: "assets/message.png",
width: 25,
height: 25,
tap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => LogList()));
},
),
buildSettingTile(
context: context,
text: 'profile.devices',
image: "assets/device.png",
width: 29,
height: 29,
tap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhoneDeviceList()));
},
),
buildSettingTile(
context: context,
text: 'change.password.title',
image: "assets/password.png",
width: 27,
height: 27,
tap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ChangePassword(mainModel.user)));
},
),
buildSettingTile(
context: context,
text: 'change.phone',
image: "assets/phone.png",
width: 30,
height: 25,
tap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ChangePhoneNumber(mainModel.user)));
},
),
buildSettingTile(
context: context,
text: 'change.email',
image: "assets/email.png",
width: 25,
height: 25,
tap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EmailPage(
user: mainModel.user,
)));
},
),
// buildSettingTile(
// context: context,
// text: 'user.block_list',
// image: "assets/block.png",
// width: 27,
// height: 27,
// tap: () {
// Navigator.push(context,
// MaterialPageRoute(builder: (context) => BlockList()));
// },
// ),
buildSettingTile(
context: context,
text: 'change.pin.title',
image: "assets/pin.png",
width: 30,
height: 30,
tap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddPINEditor(
mainModel.user,
)));
},
),
],
),
),
),
);
}
}
Widget buildSettingTile(
{@required String text,
@required BuildContext context,
@required String image,
@required double width,
@required double height,
@required GestureTapCallback tap}) {
return InkWell(
onTap: () {
tap();
},
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 5),
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 12.0, right: 20.0),
child: Image.asset(
image,
width: width,
height: height,
color: primaryColor,
),
),
LocalText(
context,
text,
fontSize: 15.0,
color: Colors.black87,
)
],
),
),
Icon(Icons.keyboard_arrow_right)
],
),
),
Divider(
color: Colors.grey,
)
],
),
);
}

179
lib/pages/quota_form.dart Normal file
View File

@@ -0,0 +1,179 @@
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/theme/theme.dart';
import 'package:fcs/vo/buyer.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
class QuotaForm extends StatefulWidget {
final BuyerProduct buyerProduct;
const QuotaForm({Key key, this.buyerProduct}) : super(key: key);
@override
_QuotaFormState createState() => _QuotaFormState();
}
class _QuotaFormState extends State<QuotaForm> {
final _formKey = GlobalKey<FormState>();
TextEditingController _product = new TextEditingController();
TextEditingController _storageQty = new TextEditingController();
TextEditingController _saleQty = new TextEditingController();
TextEditingController _dailyQuota = new TextEditingController();
TextEditingController _maxQuota = new TextEditingController();
BuyerProduct buyerProduct;
bool _isLoading = false;
@override
void initState() {
super.initState();
if (widget.buyerProduct != null) {
this.buyerProduct = widget.buyerProduct;
_product.text = widget.buyerProduct.productName;
_storageQty.text = widget.buyerProduct.storageCapacityQty == null
? ""
: widget.buyerProduct.storageCapacityQty.toString();
_saleQty.text = widget.buyerProduct.dailySaleQty == null
? ""
: widget.buyerProduct.dailySaleQty.toString();
_dailyQuota.text = widget.buyerProduct.dailyQuota == null
? ""
: widget.buyerProduct.dailyQuota.toString();
_maxQuota.text = widget.buyerProduct.maxQuota == null
? ""
: widget.buyerProduct.maxQuota.toString();
}
}
@override
Widget build(BuildContext context) {
var language = Provider.of<LanguageModel>(context);
final productbox = Container(
padding: EdgeInsets.only(top: 10),
child: Row(
children: <Widget>[
LocalText(context, "reg.table_product"),
SizedBox(
width: 30,
),
Text(_product.text, style: textStyle)
],
));
final storageQtybox = Container(
padding: EdgeInsets.only(top: 10),
child: Row(
children: <Widget>[
LocalText(context, "reg.table_storage_vol"),
SizedBox(
width: 30,
),
Text(_storageQty.text, style: textStyle)
],
));
final saleQtybox = Container(
padding: EdgeInsets.only(top: 10),
child: Row(
children: <Widget>[
LocalText(context, "reg.table_sale_vol"),
SizedBox(
width: 25,
),
Text(_saleQty.text, style: textStyle)
],
));
final dailyQuotaBox = Container(
child: TextFormField(
controller: _dailyQuota,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text('buyer.quota'),
labelStyle: language.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUpAlt,
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 quota";
}
return null;
},
),
);
final maxQuotaBox = Container(
child: TextFormField(
controller: _maxQuota,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text('buyer.max.quota'),
labelStyle: language.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUpAlt,
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 quota";
}
return null;
},
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text("Quota"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState.validate()) return;
buyerProduct.dailyQuota = int.parse(_dailyQuota.text);
buyerProduct.maxQuota = int.parse(_maxQuota.text);
Navigator.pop(context, buyerProduct);
},
)
],
),
body: Form(
key: _formKey,
child: Container(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
productbox,
storageQtybox,
saleQtybox,
dailyQuotaBox,
maxQuotaBox
],
),
),
)),
);
}
}

216
lib/pages/quota_page.dart Normal file
View File

@@ -0,0 +1,216 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/buyer_model.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/vo/buyer.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
class QuotaPage extends StatefulWidget {
final Buyer buyer;
final bool isApproved;
const QuotaPage({this.buyer, this.isApproved});
@override
_QuotaPageState createState() => _QuotaPageState();
}
class _QuotaPageState extends State<QuotaPage> {
final formKey = GlobalKey<FormState>();
TextEditingController _dailyQuota = new TextEditingController();
TextEditingController _maxQuota = new TextEditingController();
TextEditingController _companyName = new TextEditingController();
TextEditingController _accountName = new TextEditingController();
Buyer buyer = Buyer();
bool _isLoading = false;
@override
void initState() {
super.initState();
if (widget.buyer != null) {
this.buyer = widget.buyer;
_companyName.text = buyer.bizName;
_accountName.text = buyer.userName;
_dailyQuota.text = buyer.dailyQuota.toString();
_maxQuota.text = buyer.maxQuota.toString();
Provider.of<BuyerModel>(context, listen: false)
.loadBuyerProducts(buyer, force: true)
.then((b) {
setState(() {
buyer = b;
});
});
}
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
final companyNameBox = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 20),
child: TextFormField(
controller: _companyName,
autofocus: false,
cursorColor: primaryColor,
readOnly: true,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("reg.biz_name"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
border: InputBorder.none,
focusedBorder: InputBorder.none,
icon: Icon(
Icons.business,
color: primaryColor,
),
),
));
final accountNameBox = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 20),
child: TextFormField(
controller: _accountName,
autofocus: false,
cursorColor: primaryColor,
readOnly: true,
style: textStyle,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("buyer.account_name"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
border: InputBorder.none,
focusedBorder: InputBorder.none,
icon: Icon(
Icons.business,
color: primaryColor,
),
),
));
final dailyQuotaBox = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 15),
child: TextFormField(
controller: _dailyQuota,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("reg.quota"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
Icons.business,
color: primaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
),
validator: _validateQuota,
));
final maxQuotaBox = Container(
padding: EdgeInsets.only(top: 0, left: 20, right: 15),
child: TextFormField(
controller: _maxQuota,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
labelText: AppTranslations.of(context).text("reg.max_quota"),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
Icons.business,
color: primaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
),
validator: _validateQuota,
));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text("Quota"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send),
onPressed: () {
showConfirmDialog(context, "buyer.allocate.quota.confirm", () {
_allocate();
});
},
)
],
),
body: Form(
key: formKey,
child: Container(
padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10),
child: ListView(
children: <Widget>[
Container(
height: 500,
child: Card(
elevation: 23,
child: Column(
children: <Widget>[
accountNameBox,
companyNameBox,
dailyQuotaBox,
maxQuotaBox,
],
),
),
)
],
),
),
),
),
);
}
String _validateQuota(value) {
if (value.isEmpty) {
return "Invalid number";
}
return null;
}
_allocate() async {
if (!formKey.currentState.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
buyer.dailyQuota = int.parse(_dailyQuota.text);
buyer.maxQuota = int.parse(_maxQuota.text);
if (widget.isApproved) {
await Provider.of<BuyerModel>(context).allocate(buyer);
Navigator.pop(context, true);
} else {
Navigator.pop<Buyer>(context, this.buyer);
}
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,260 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/report_user_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/report.dart';
import 'package:fcs/vo/report_user.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 'util.dart';
typedef void FindCallBack();
class ReportUserEditor extends StatefulWidget {
final Report report;
const ReportUserEditor({this.report});
@override
_ReportUserEditorState createState() => _ReportUserEditorState();
}
class _ReportUserEditorState extends State<ReportUserEditor> {
TextEditingController _name = new TextEditingController();
TextEditingController _searchInput = new TextEditingController();
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool _isLoading = false;
bool isSend = false;
User selectedUser;
List<User> _users = [];
int selectedIndex;
@override
void initState() {
super.initState();
}
Widget searchInputBox(BuildContext context, FindCallBack findCallBack) {
var languageModel = Provider.of<LanguageModel>(context);
return Container(
padding: EdgeInsets.only(top: 10, left: 15, right: 15),
child: Stack(
alignment: const Alignment(1.2, 1.0),
children: <Widget>[
TextFormField(
controller: _searchInput,
autofocus: false,
cursorColor: primaryColor,
style: textStyle,
decoration: new InputDecoration(
labelText:
AppTranslations.of(context).text('report.user.search'),
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: primaryColor, width: 1.0)),
),
),
new FlatButton(
onPressed: () {
findCallBack();
},
child: new Icon(
Icons.search,
size: 25,
))
],
));
}
@override
Widget build(BuildContext context) {
final namebox = Container(
padding: EdgeInsets.only(left: 10, top: 10),
child: TextFormField(
controller: _name,
autofocus: false,
readOnly: true,
cursorColor: primaryColor,
decoration: new InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
icon: Icon(
Icons.person,
color: primaryColor,
),
),
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(context, "user.title",
fontSize: 20, color: Colors.white),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.save,
color: Colors.white,
),
onPressed: () {
_save(context);
})
],
),
body: Column(
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
searchInputBox(context, () => _findUser(context)),
this.isSend ? namebox : Container(),
Container(
padding: EdgeInsets.only(
top: this.isSend ? 10 : 20, left: 20, right: 20),
child: Column(
children: _getUsers(context),
),
),
],
),
),
SizedBox(
height: 20,
)
],
)),
);
}
_findUser(BuildContext context) async {
var reportUserModel = Provider.of<ReportUserModel>(context);
if (_searchInput.text == '') {
showMsgDialog(context, "Error", 'Please fill the search field');
}
setState(() {
_isLoading = true;
});
try {
List<User> users = await reportUserModel.findUser(_searchInput.text);
if (users.isEmpty) return;
setState(() {
this._users = users;
});
} catch (e) {
setState(() {
this.isSend = false;
});
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
List<Widget> _getUsers(BuildContext context) {
return _users.asMap().entries.map((u) {
return Container(
child: Card(
elevation: 10,
color: Colors.white,
child: InkWell(
onTap: () {
setState(() {
this.selectedUser = u.value;
isSend = true;
_name.text = selectedUser.name;
selectedIndex = u.key;
});
},
child: Row(
children: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: new Row(
children: <Widget>[
new Padding(
padding: new EdgeInsets.symmetric(
horizontal: 32.0 - 15.0 / 2),
child: Icon(
Icons.account_circle,
color: Colors.grey,
size: 50,
),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
u.value.name == null ? "" : u.value.name,
style: new TextStyle(
fontSize: 15.0, color: Colors.black),
),
],
),
],
),
),
),
selectedIndex != null && selectedIndex == u.key
? Container(
padding: EdgeInsets.only(right: 25),
child: Icon(Icons.check))
: Container()
],
),
),
));
}).toList();
}
_save(BuildContext context) async {
var reportUserModel = Provider.of<ReportUserModel>(context);
if (selectedUser == null) return;
setState(() {
_isLoading = true;
});
reportUserModel.getUsersForReport(widget.report.id).then((users) async {
if (users.any((u) => u.userID == this.selectedUser.docID)) {
setState(() {
_isLoading = false;
});
showMsgDialog(context, "Error", 'Duplicate User');
} else {
try {
ReportUser _reportUser = ReportUser(
userID: this.selectedUser.docID,
userName: this.selectedUser.name,
reportID: widget.report.id,
reportName: widget.report.display);
await reportUserModel.assignUser(_reportUser);
reportUserModel.getUsersForReport(widget.report.id).then((users) {
Navigator.pop<List<ReportUser>>(context, users);
});
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
});
}
}

View File

@@ -0,0 +1,220 @@
import 'package:provider/provider.dart';
import 'package:fcs/model/report_user_model.dart';
import 'package:fcs/vo/report.dart';
import 'package:fcs/vo/report_user.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:flutter/material.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
import 'report_user_editor.dart';
import 'util.dart';
class ReportUserList extends StatefulWidget {
final Report report;
const ReportUserList({Key key, this.report}) : super(key: key);
@override
_ReportUserListState createState() => _ReportUserListState();
}
class _ReportUserListState extends State<ReportUserList> {
Report _report = new Report();
final double dotSize = 15.0;
bool _isLoading = false;
List<ReportUser> _users = [];
bool isForAllUsers = false;
@override
void initState() {
super.initState();
if (widget.report != null) {
this._report = widget.report;
var reportUserModel =
Provider.of<ReportUserModel>(context, listen: false);
reportUserModel.getUsersForReport(this._report.id).then((users) {
if (mounted) {
setState(() {
this._users = users;
});
}
});
this.isForAllUsers =
widget.report.forAllUser == null ? false : widget.report.forAllUser;
}
}
@override
Widget build(BuildContext context) {
final allUserBox = Container(
child: new ListTile(
title: new Row(
children: <Widget>[
new Checkbox(
value: isForAllUsers,
activeColor: primaryColor,
onChanged: (bool value) async {
setState(() {
_isLoading = true;
});
try {
setState(() {
this.isForAllUsers = value;
});
this._report.forAllUser = this.isForAllUsers;
var reportUserModel = Provider.of<ReportUserModel>(context);
await reportUserModel.updateReportForAllUsers(this._report);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
'All Users',
style: TextStyle(
fontSize: 15.0,
),
),
],
),
],
)));
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
'report.users.title',
translationVariables: [this._report.display],
color: Colors.white,
fontSize: 18,
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: () async {
List<ReportUser> _us = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ReportUserEditor(report: widget.report)),
);
if (_us == null) return;
setState(() {
_users.clear();
_users.addAll(_us);
});
},
),
body: ListView(
shrinkWrap: true,
children: <Widget>[
allUserBox,
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Column(
children: _getUserRow(context),
),
),
SizedBox(height: 15)
],
),
),
);
}
List<Widget> _getUserRow(BuildContext context) {
_users.sort((a, b) => a.userName.compareTo(b.userName));
return _users.map((u) {
return Container(
child: Card(
elevation: 10,
color: Colors.white,
child: InkWell(
onTap: () {},
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.account_circle,
color: primaryColor,
size: 50,
),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
u.userName == null ? "" : u.userName,
style: new TextStyle(
fontSize: 17.0, color: Colors.black),
),
],
),
],
),
),
),
IconButton(
padding: EdgeInsets.only(right: 10),
icon: Icon(
Icons.delete,
color: Colors.grey[700],
),
onPressed: () {
showConfirmDialog(context, "report.user_delete_confirm",
() {
_delete(context, u);
});
})
],
),
),
));
}).toList();
}
void _delete(BuildContext context, ReportUser reportUser) async {
setState(() {
_isLoading = true;
});
try {
var reportUserModel = Provider.of<ReportUserModel>(context);
await reportUserModel.deleteReportUser(reportUser);
reportUserModel.getUsersForReport(widget.report.id).then((users) {
if (mounted) {
setState(() {
this._users = users;
});
}
});
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,368 @@
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/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 ResetPasswordPage extends StatefulWidget {
final String phoneNumber;
ResetPasswordPage(
this.phoneNumber, {
Key key,
}) : super(key: key);
@override
_ResetPasswordPageState createState() => new _ResetPasswordPageState();
}
class _ResetPasswordPageState extends State<ResetPasswordPage>
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,
'reset.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: myFocusNodeEmail,
controller: _smsController,
keyboardType: TextInputType.phone,
style: TextStyle(
fontFamily: "WorkSansSemiBold",
fontSize: 16.0,
color: Colors.black),
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(
FontAwesomeIcons.sms,
color: Colors.black,
),
labelText: AppTranslations.of(context)
.text("reset.sms"),
labelStyle:
Provider.of<LanguageModel>(context).isEng
? TextStyle(
fontFamily: "WorkSansSemiBold",
color: Colors.grey)
: TextStyle(
fontFamily: "MyanmarUnicode",
color: Colors.grey),
),
validator: _validateSMSCode),
),
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: _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("reset.new_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,
),
),
],
),
),
),
),
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("reset"),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle(
color: Colors.white,
fontSize: 18.0,
fontFamily: "WorkSansBold")
: TextStyle(
color: Colors.white,
fontSize: 18.0,
fontFamily: "MyanmarUnicode"),
),
),
onPressed: () => _reset(context)),
),
],
),
],
),
);
}
void _toggleLogin() {
setState(() {
_obscureTextLogin = !_obscureTextLogin;
});
}
void _toggleSignup() {
setState(() {
_obscureTextSignup = !_obscureTextSignup;
});
}
void _toggleSignupConfirm() {
setState(() {
_obscureTextSignupConfirm = !_obscureTextSignupConfirm;
});
}
void _reset(BuildContext context) async {
if (!formKey.currentState.validate()) {
return;
}
var smsCode = _smsController.text;
var password = _passwordController.text;
setState(() {
_isLoading = true;
});
UserModel userModel = Provider.of<UserModel>(context);
try {
await userModel.resetPassword(widget.phoneNumber, password, smsCode);
Navigator.pushNamedAndRemoveUntil(context, "/login", (r) => false);
} 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.confirm_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;
}
String _validateSMSCode(value) {
if (value.isEmpty) {
return AppTranslations.of(context).text("login.sms_empty");
}
if (value.length != 6) {
return AppTranslations.of(context).text("login.sms_size");
}
return null;
}
}

113
lib/pages/search_page.dart Normal file
View File

@@ -0,0 +1,113 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/buyer_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/buyer.dart';
import 'buyer_list_row.dart';
Future<Buyer> showPlacesSearch(BuildContext context) async =>
await showSearch<Buyer>(
context: context,
delegate: UserSearchDelegate(),
);
class UserSearchDelegate extends SearchDelegate<Buyer> {
@override
ThemeData appBarTheme(BuildContext context) {
final ThemeData theme = Theme.of(context);
return theme.copyWith(
inputDecorationTheme: InputDecorationTheme(
hintStyle: TextStyle(
color: theme.primaryTextTheme.title.color, fontSize: 16)),
primaryColor: primaryColor,
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.white),
primaryColorBrightness: Brightness.light,
primaryTextTheme: theme.textTheme,
textTheme: theme.textTheme.copyWith(
title: theme.textTheme.title.copyWith(
color: theme.primaryTextTheme.title.color, fontSize: 16)),
);
}
@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () => query = '',
),
];
}
@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => close(context, null),
);
}
@override
Widget buildResults(BuildContext context) {
final buyerModel = Provider.of<BuyerModel>(context);
return FutureBuilder(
future: buyerModel.search(query),
builder: (context, AsyncSnapshot<List<Buyer>> snapshot) {
if (snapshot.hasData) {
if (snapshot.data.length == 0) {
return Container(
child: Center(
child: Text(
"Error :No Search Buyer",
textAlign: TextAlign.center,
),
),
);
}
return Container(
padding: EdgeInsets.only(top: 15),
child: ListView(
children:
snapshot.data.map((u) => BuyerListRow(buyer: u)).toList(),
),
);
} else if (snapshot.hasError) {
return Container(
child: Center(
child: Text(
'${snapshot.error}',
textAlign: TextAlign.center,
),
),
);
} else {
return Container(
child: Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(primaryColor)),
),
);
}
});
}
@override
Widget buildSuggestions(BuildContext context) {
return Container(
child: Center(
child: Opacity(
opacity: 0.2,
child: Icon(
Icons.supervised_user_circle,
size: 200,
)),
),
);
}
}

View File

@@ -0,0 +1,416 @@
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/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/progress.dart';
class SettingEidtor extends StatefulWidget {
final Setting setting;
const SettingEidtor({this.setting});
@override
_SettingEidtorState createState() => _SettingEidtorState();
}
class _SettingEidtorState extends State<SettingEidtor> {
TextEditingController _doExpire = new TextEditingController();
TextEditingController _poExpire = new TextEditingController();
TextEditingController _poOpend = new TextEditingController();
TextEditingController _poClosed = new TextEditingController();
TextEditingController _latestDeliveryDay = new TextEditingController();
TextEditingController _firstStorageDay = new TextEditingController();
TextEditingController _firstStorageCharge = new TextEditingController();
TextEditingController _secondStorageDay = new TextEditingController();
TextEditingController _secondStorgeCharge = new TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
List<Day> days = [];
@override
void initState() {
super.initState();
days = dayLists;
if (widget.setting != null) {
_doExpire.text = widget.setting.doExpireInHours.toString();
_poExpire.text = widget.setting.poExpireInHours.toString();
_poOpend.text = widget.setting.poOpenAt.toString();
_poClosed.text = widget.setting.poCloseAt.toString();
_latestDeliveryDay.text =widget.setting.latestDeliveryDay.toString();
_firstStorageDay.text = widget.setting.firstStorageChargeIn.toString();
_firstStorageCharge.text = widget.setting.firstStorageCharge.toString();
_secondStorageDay.text = widget.setting.secondStorageChargeIn.toString();
_secondStorgeCharge.text = widget.setting.secondStorageCharge.toString();
days.forEach((d) => widget.setting.poCloseOn.contains(d.id)
? d.isChecked = true
: d.isChecked = false);
}
}
Widget showDayList(BuildContext context, MainModel mainModel) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5.0),
height: 75.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 10, top: 10),
child: Text(
"PO submission closed Day",
style: TextStyle(color: Colors.black54),
),
),
Expanded(
child: ListView.builder(
itemCount: days.length,
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(top: 10),
itemBuilder: (BuildContext context, int index) {
return new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Checkbox(
value: days[index].isChecked == null
? false
: days[index].isChecked,
activeColor: primaryColor,
onChanged: (bool value) {
setState(() {
days[index].isChecked = value;
});
}),
Container(
padding: EdgeInsets.only(top: 13),
child: new Text(
dayLists[index].name,
style: TextStyle(
fontSize: 15.0,
),
),
),
],
);
}),
),
],
),
);
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
var mainModel = Provider.of<MainModel>(context);
final doExpireBox = TextFormField(
controller: _doExpire,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'DO expired Time',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.clock,
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 DO expired Time';
}
return null;
},
);
final poExpireBox = TextFormField(
controller: _poExpire,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'PO expired Time',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.clock,
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 PO expired Time';
}
return null;
},
);
final poOpenedBox = TextFormField(
controller: _poOpend,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'PO submission opened Time',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.clock,
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 PO submission opened Time';
}
return null;
},
);
final poClosedBox = TextFormField(
controller: _poClosed,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'PO submission closed Time',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.clock,
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 PO submission closed Time';
}
return null;
},
);
final latestDeliveryDayBox = TextFormField(
controller: _latestDeliveryDay,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'Latest Delivery Day',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.clock,
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 Latest Delivery Day';
}
return null;
},
);
final firstStorageDayBox = TextFormField(
controller: _firstStorageDay,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'First storage charge starts Day',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.calendarDay,
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 First storage charge starts Day';
}
return null;
},
);
final firstStorgeChargeBox = TextFormField(
controller: _firstStorageCharge,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'First storage charge Fees',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUp,
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 First storage charge Fees';
}
return null;
},
);
final secondStorgeDayBox = TextFormField(
controller: _secondStorageDay,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'Second storage charge starts Day',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.calendarDay,
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 Second storage charge starts Day';
}
return null;
},
);
final secondStorgeChargeBox = TextFormField(
controller: _secondStorgeCharge,
autofocus: false,
cursorColor: primaryColor,
keyboardType: TextInputType.phone,
style: textStyle,
decoration: new InputDecoration(
labelText: 'Second storage charge Fees',
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUp,
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 Second storage charge Fees';
}
return null;
},
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
"setting.title",
fontSize: 20,
color: Colors.white,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send),
onPressed: () {
if (!_formKey.currentState.validate()) return;
showConfirmDialog(context, "setting.confirm", () {
_submit();
});
})
],
),
body: Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
// doExpireBox,
poExpireBox,
poOpenedBox,
poClosedBox,
showDayList(context, mainModel),
latestDeliveryDayBox,
firstStorageDayBox,
firstStorgeChargeBox,
secondStorgeDayBox,
secondStorgeChargeBox
],
),
),
));
}
_submit() async {
setState(() {
_isLoading = true;
});
try {
widget.setting.poExpireInHours = int.parse(_poExpire.text);
widget.setting.poOpenAt = int.parse(_poOpend.text);
widget.setting.poCloseAt = int.parse(_poClosed.text);
widget.setting.latestDeliveryDay=int.parse(_latestDeliveryDay.text);
widget.setting.firstStorageChargeIn = int.parse(_firstStorageDay.text);
widget.setting.firstStorageCharge = int.parse(_firstStorageCharge.text);
widget.setting.secondStorageChargeIn = int.parse(_secondStorageDay.text);
widget.setting.secondStorageCharge = int.parse(_secondStorgeCharge.text);
widget.setting.poCloseOn =
this.days.where((d) => d.isChecked == true).map((p) => p.id).toList();
var mainModel = Provider.of<MainModel>(context);
await mainModel.updateSetting(widget.setting);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,256 @@
import 'package:flutter/material.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/setting.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
class Time {
final int value;
Time(this.value);
String get getValue =>
value > 12 ? (value - 12).toString() + "PM" : value.toString() + "AM";
@override
bool operator ==(other) {
if (identical(this, other)) {
return true;
}
return other.value == this.value;
}
@override
int get hashCode {
int result = 17;
result = 37 * result + value.hashCode;
return result;
}
}
class SettingOwner extends StatefulWidget {
final Setting setting;
const SettingOwner({this.setting});
@override
_SettingOwnerState createState() => _SettingOwnerState();
}
class _SettingOwnerState extends State<SettingOwner> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
List<Day> days = [];
int poOpenAt = 0;
int poCloseAt = 0;
@override
void initState() {
super.initState();
days = dayLists;
if (widget.setting != null) {
poOpenAt = widget.setting.poOpenAt;
poCloseAt = widget.setting.poCloseAt;
days.forEach((d) => widget.setting.poCloseOn.contains(d.id)
? d.isChecked = true
: d.isChecked = false);
}
}
Widget showDayList(BuildContext context, MainModel mainModel) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5.0),
height: 500.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 10, top: 10),
child: Text(
"PO submission closed Day",
style: TextStyle(color: Colors.black54),
),
),
Expanded(
child: ListView.builder(
itemCount: days.length,
scrollDirection: Axis.vertical,
padding: EdgeInsets.only(top: 10),
itemBuilder: (BuildContext context, int index) {
return new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Checkbox(
value: days[index].isChecked == null
? false
: days[index].isChecked,
activeColor: primaryColor,
onChanged: (bool value) {
setState(() {
days[index].isChecked = value;
});
}),
Container(
padding: EdgeInsets.only(top: 13),
child: new Text(
dayLists[index].name,
style: TextStyle(
fontSize: 15.0,
),
),
),
],
);
}),
),
],
),
);
}
@override
Widget build(BuildContext context) {
var mainModel = Provider.of<MainModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
"setting.title",
fontSize: 20,
color: Colors.white,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send),
onPressed: () {
if (!_formKey.currentState.validate()) return;
showConfirmDialog(context, "setting.confirm", () {
_submit();
});
})
],
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 24),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("PO submission opened at:"),
),
getDropDown(poOpenAt, (value) {
setState(() {
setState(() {
this.poOpenAt = value;
});
});
}),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 24),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("PO submission closed at:"),
),
getDropDown(poCloseAt, (value) {
setState(() {
setState(() {
this.poCloseAt = value;
});
});
}),
],
),
),
Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
showDayList(context, mainModel),
],
),
),
],
),
));
}
Widget getDropDown(int initial, Function(int) onChanged) {
Time value = Time(initial);
return DropdownButton<Time>(
value: value,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: primaryColor,
),
onChanged: (value) => onChanged(value.value),
items: <Time>[
Time(0),
Time(1),
Time(2),
Time(3),
Time(4),
Time(5),
Time(6),
Time(7),
Time(8),
Time(9),
Time(10),
Time(11),
Time(12),
Time(13),
Time(14),
Time(15),
Time(16),
Time(17),
Time(18),
Time(19),
Time(20),
Time(21),
Time(22),
Time(23),
].map<DropdownMenuItem<Time>>((Time value) {
return DropdownMenuItem<Time>(
value: value,
child: Text(value.getValue),
);
}).toList(),
);
}
_submit() async {
setState(() {
_isLoading = true;
});
try {
widget.setting.poCloseOn =
this.days.where((d) => d.isChecked == true).map((p) => p.id).toList();
widget.setting.poOpenAt = poOpenAt;
widget.setting.poCloseAt = poCloseAt;
var mainModel = Provider.of<MainModel>(context);
await mainModel.updateSetting(widget.setting);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

198
lib/pages/settings.dart Normal file
View File

@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/pages/setting_editor.dart';
import 'package:fcs/pages/setting_editor_byOwner.dart';
import 'package:fcs/vo/setting.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
import '../theme/theme.dart';
class Settings extends StatefulWidget {
@override
_SettingState createState() => _SettingState();
}
class _SettingState extends State<Settings> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: LocalText(
context,
"setting.title",
fontSize: 20,
color: Colors.white,
),
backgroundColor: primaryColor,
actions: <Widget>[
mainModel.isBizAdmin() || mainModel.isSysAdmin()
? IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingEidtor(
setting: mainModel.setting,
)),
);
})
: Container()
],
),
body: SingleChildScrollView(
padding: EdgeInsets.only(
left: 25.0,
right: 25.0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ListTile(
dense: true,
title: Text("PO will expire in"),
subtitle: Text("After PO is submitted"),
trailing: Text(
mainModel.setting.poExpireInHours.toString() + " hours"),
),
Divider(color: secondaryColor),
Container(
alignment: Alignment.topRight,
child: mainModel.isOwnerAndAbove() || mainModel.isAdmin()
? IconButton(
padding: EdgeInsets.all(0),
visualDensity: VisualDensity.compact,
icon: Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingOwner(
setting: mainModel.setting,
)),
);
})
: SizedBox(),
),
ListTile(
dense: true,
title: Text("PO submission opened at"),
trailing: Text("${mainModel.setting.getPoOpenAt}"),
),
ListTile(
dense: true,
title: Text("PO submission closed at"),
trailing: Text("${mainModel.setting.getPoCloseAt}"),
),
ListTile(
dense: true,
title: Text("PO submission closed on"),
subtitle: Text(
mainModel.setting.getPoCloseOn,
style: textStyleOdd,
),
),
Divider(color: secondaryColor),
ListTile(
dense: true,
title: Text("Latest Delivery Day at"),
trailing: Text("${mainModel.setting.latestDeliveryDay} days"),
),
Divider(color: secondaryColor),
ListTile(
dense: true,
title: Text("First storage charge starts in"),
subtitle: Text("After PO is approved"),
trailing:
Text("${mainModel.setting.firstStorageChargeIn} days"),
),
ListTile(
dense: true,
title: Text(
"First storage charge ",
style: TextStyle(backgroundColor: Colors.greenAccent),
),
trailing:
Text("${mainModel.setting.firstStorageCharge} Kyats/Liter"),
),
Divider(color: secondaryColor),
ListTile(
dense: true,
title: Text("Second storage charge starts in"),
subtitle: Text("After PO is approved"),
trailing:
Text("${mainModel.setting.secondStorageChargeIn} days"),
),
ListTile(
dense: true,
title: Text(
"Second storage charge ",
style: TextStyle(backgroundColor: Colors.amber),
),
trailing: Text(
"${mainModel.setting.secondStorageCharge} Kyats/Liter"),
),
],
),
),
),
);
}
}
Widget buildSettingTile(
{@required String text,
@required BuildContext context,
@required String image,
@required GestureTapCallback tap}) {
return InkWell(
onTap: () {
tap();
},
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 5),
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 12.0, right: 20.0),
child: Image.asset(
image,
width: 28,
height: 28,
color: primaryColor,
),
),
LocalText(
context,
text,
fontSize: 15.0,
color: Colors.black87,
)
],
),
),
Icon(Icons.keyboard_arrow_right)
],
),
),
Divider(
color: Colors.grey,
)
],
),
);
}

103
lib/pages/sign.dart Normal file
View File

@@ -0,0 +1,103 @@
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_signature_pad/flutter_signature_pad.dart';
class SignPage extends StatefulWidget {
SignPage({Key key}) : super(key: key);
@override
_SignPageState createState() => _SignPageState();
}
class _SignPageState extends State<SignPage> {
ByteData _img = ByteData(0);
var color = Colors.blue;
var strokeWidth = 5.0;
final _sign = GlobalKey<SignatureState>();
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: Text("Driver Signature"),
),
Expanded(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Signature(
color: color,
key: _sign,
onSign: () {
final sign = _sign.currentState;
},
strokeWidth: strokeWidth,
),
),
color: Colors.black12,
),
),
_img.buffer.lengthInBytes == 0
? Container()
: LimitedBox(
maxHeight: 200.0,
child: Image.memory(_img.buffer.asUint8List())),
Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: MaterialButton(
color: Colors.green,
onPressed: () async {
final sign = _sign.currentState;
final image = await sign.getData();
var data = await image.toByteData(
format: ui.ImageByteFormat.png);
Uint8List buffer = data.buffer.asUint8List();
// String path = await _localPath;
// File file = await new File(path + "/sign.png")
// .writeAsBytes(buffer.asUint8List(
// data.offsetInBytes, data.lengthInBytes));
Navigator.pop(context, buffer);
},
child: Text("OK")),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: MaterialButton(
color: Colors.grey,
onPressed: () {
final sign = _sign.currentState;
sign.clear();
setState(() {
_img = ByteData(0);
});
},
child: Text("Clear")),
),
],
),
],
)
],
),
);
}
}

103
lib/pages/sms_page.dart Normal file
View File

@@ -0,0 +1,103 @@
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/theme/theme.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
import 'util.dart';
class SmsCodePage extends StatefulWidget {
final String id, password;
const SmsCodePage({Key key, this.id, this.password}) : super(key: key);
@override
_SmsCodePageState createState() => _SmsCodePageState();
}
class _SmsCodePageState extends State<SmsCodePage> {
final TextEditingController _sms = new TextEditingController();
bool _isLoading = false;
final _formKey = GlobalKey<FormState>();
_confimSignin() async {
MainModel mainModel = Provider.of<MainModel>(context);
if (!_formKey.currentState.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
await mainModel.confirmSignup(widget.id, widget.password, _sms.text);
await mainModel.login(widget.id, widget.password);
Navigator.pushNamedAndRemoveUntil(context, "/", (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: AppTranslations.of(context).text("sms.sms"),
labelStyle: labelStyle,
hintText: 'eg. 123456',
icon: Icon(
Icons.lock,
color: primaryColor,
)),
validator: (value) {
if (value.isEmpty) {
return AppTranslations.of(context).text("sms.empty");
}
return null;
},
);
final singInButton = Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () => _confimSignin(),
padding: EdgeInsets.all(12),
color: primaryColor,
child: Text(AppTranslations.of(context).text("singin"),
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: Text(AppTranslations.of(context).text("input_sms")),
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),
singInButton,
],
),
),
),
);
}
}

137
lib/pages/splash.dart Normal file
View File

@@ -0,0 +1,137 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:google_api_availability/google_api_availability.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/model/shared_pref.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
class SplashScreen extends StatefulWidget {
@override
_SplashScreenState createState() => new _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
final log = Logger('_SplashScreenState');
bool _loaded = false;
bool _isSupport = false;
bool _isGoogleService = true;
bool _isLogin = false;
bool _isAgree = false;
bool _hasEmail = false;
bool _isOnline = true;
Timer timer;
startTime() async {
var _duration = new Duration(milliseconds: 500);
this.timer = new Timer.periodic(_duration, navigationPage);
}
void navigationPage(Timer timer) async {
if (!_isOnline) {
return;
}
// GooglePlayServicesAvailability availability = await GoogleApiAvailability
// .instance
// .checkGooglePlayServicesAvailability(true);
// log.info("GooglePlaysServcie Result1:$availability");
// if (availability != GooglePlayServicesAvailability.success &&
// Platform.isAndroid) {
// timer.cancel();
// setState(() {
// _isGoogleService = false;
// });
// return;
// }
if (_loaded) {
timer.cancel();
Navigator.of(context).pushReplacementNamed('/home');
// if (_isSupport) {
// if (_isLogin) {
// if (!_isAgree) {
// await Navigator.of(context).pushNamed('/term');
// startTime();
// } else {
// bool skipped = await SharedPref.getSkippedRecoverEmail();
// skipped = skipped ?? false;
// if (!this._hasEmail && !skipped) {
// Navigator.of(context).pushReplacementNamed('/email');
// } else {
// }
// }
// } else {
// Navigator.of(context).pushReplacementNamed('/welcome');
// }
// }
}
}
@override
void initState() {
super.initState();
startTime();
}
void dispose() {
super.dispose();
if (timer.isActive) timer.cancel();
}
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
this._loaded = mainModel.isLoaded;
this._isSupport = mainModel.isSupport();
this._isLogin = mainModel.isLogin();
this._isAgree = mainModel.agreedTerm();
this._hasEmail = mainModel.hasEmail();
this._isOnline = mainModel.isOnline;
return new Scaffold(
backgroundColor: Colors.white,
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.asset(
"assets/logo.png",
width: 180,
),
SizedBox(height: 50),
CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(primaryColor),
),
SizedBox(height: 30),
_isOnline
? Container()
: Column(
children: <Widget>[
LocalText(context, "offline.status"),
],
),
Text(_isGoogleService ? "" : "Google Play service not found."),
Text(
_loaded
? (!_isSupport
? "Version outdated, please update your app!"
: "")
: AppTranslations.of(context).text("load"),
style: TextStyle(
color: primaryColor, fontWeight: FontWeight.bold)),
],
),
),
);
}
}

View File

@@ -0,0 +1,249 @@
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/product_model.dart';
import 'package:fcs/model/storage_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/inventory_line.dart';
import 'package:fcs/vo/product.dart';
import 'package:fcs/vo/storage.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
class InventoryItem extends StatefulWidget {
final InventoryLine inventoryLine;
const InventoryItem({Key key, this.inventoryLine}) : super(key: key);
@override
_InventoryItemState createState() => _InventoryItemState();
}
class _InventoryItemState extends State<InventoryItem> {
InventoryLine inventoryLine = new InventoryLine();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
String currentStorageID, currentProductID;
TextEditingController _volumeController = new TextEditingController();
TextEditingController _oldVolume = new TextEditingController();
@override
void initState() {
super.initState();
if (widget.inventoryLine != null) {
this.inventoryLine = widget.inventoryLine;
this.inventoryLine.action = "update";
_oldVolume.text = this.inventoryLine.oldQty.toString();
} else {
this.inventoryLine.action = "create";
}
_volumeController.text =
inventoryLine.quantity == null ? "" : inventoryLine.quantity.toString();
this.currentStorageID = inventoryLine.storageID;
this.currentProductID = inventoryLine.productID;
}
Widget showStorageList(BuildContext context, StorageModel storageModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Image.asset(
"assets/inventory.png",
color: primaryColor,
width: 25,
),
SizedBox(
width: 18,
),
new Flexible(
child: Container(
width: 170.0,
child: DropdownButton<String>(
value: currentStorageID,
isExpanded: true,
hint: Text(
'Select Storage',
style: labelStyle,
),
onChanged: changedDropDownItem,
items: storageModel.storages
.map<DropdownMenuItem<String>>((Storage storage) {
return new DropdownMenuItem<String>(
value: storage.id,
child: new Text(storage.name, style: textStyle),
);
}).toList(),
),
),
),
],
);
}
void changedDropDownItem(selected) {
setState(() {
currentStorageID = selected;
});
}
Widget showProducts(BuildContext context, ProductModel productModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(
FontAwesomeIcons.tag,
color: primaryColor,
size: 20,
),
SizedBox(
width: 20,
),
new Flexible(
child: Container(
width: 170.0,
child: DropdownButton<String>(
value: currentProductID,
isExpanded: true,
hint: Text(
'Select Product',
style: labelStyle,
),
onChanged: changedProduct,
items: productModel.products
.map<DropdownMenuItem<String>>((Product product) {
return new DropdownMenuItem<String>(
value: product.id,
child: new Text(product.name, style: textStyle),
);
}).toList(),
),
),
),
],
);
}
void changedProduct(selected) {
setState(() {
currentProductID = selected;
});
}
@override
Widget build(BuildContext context) {
var storageModel = Provider.of<StorageModel>(context);
var productModel = Provider.of<ProductModel>(context);
var languageModel = Provider.of<LanguageModel>(context);
final oldVolumeBox = Container(
padding: EdgeInsets.only(left: 5, top: 15, bottom: 10),
child: Row(
children: <Widget>[
LocalText(context, "inventory.old.qty"),
Container(
padding: EdgeInsets.only(left: 20),
child: Text(
_oldVolume.text,
style: textStyle,
),
)
],
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
'inventory.item',
fontSize: 20,
color: Colors.white,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_delete();
},
),
IconButton(
icon: Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState.validate()) return;
_save();
},
)
],
),
body: Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
widget.inventoryLine == null ? Container() : oldVolumeBox,
showStorageList(context, storageModel),
showProducts(context, productModel),
Container(
child: TextFormField(
keyboardType: TextInputType.number,
controller: _volumeController,
autofocus: false,
cursorColor: primaryColor,
decoration: new InputDecoration(
labelText:
AppTranslations.of(context).text("inventory.new.qty"),
labelStyle:
languageModel.isEng ? labelStyle : labelStyleMM,
icon: Icon(
FontAwesomeIcons.sortNumericUpAlt,
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("inventory.form.qty");
}
return null;
},
),
),
],
),
)),
);
}
_save() {
if (currentProductID == null || currentStorageID == null) return;
this.inventoryLine.storageID = currentStorageID;
var storageName =
Provider.of<StorageModel>(context).getStorageName(currentStorageID);
this.inventoryLine.storageName = storageName;
this.inventoryLine.productID = currentProductID;
var productName =
Provider.of<ProductModel>(context).getProductName(currentProductID);
this.inventoryLine.productName = productName;
this.inventoryLine.quantity = int.parse(_volumeController.text);
Navigator.pop<InventoryLine>(context, this.inventoryLine);
}
_delete() {
this.inventoryLine.action = "delete";
Navigator.pop<InventoryLine>(context, this.inventoryLine);
}
}

View File

@@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/storage_model.dart';
import 'package:fcs/pages/storage/inventory_item.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/inventory_line.dart';
import 'package:fcs/vo/inventory_taking.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/my_data_table.dart';
import 'package:fcs/widget/progress.dart';
class TakeInventory extends StatefulWidget {
@override
_TakeInventoryState createState() => _TakeInventoryState();
}
class _TakeInventoryState extends State<TakeInventory> {
InventoryTaking inventoryTaking = InventoryTaking();
bool _isLoading = false;
@override
void initState() {
super.initState();
var _storages = Provider.of<StorageModel>(context, listen: false).storages;
_storages.forEach((s) {
s.products.forEach((p) {
inventoryTaking.inventoryLines.add(InventoryLine(
productID: p.id,
productName: p.name,
storageID: p.storageID,
storageName: p.storageName,
oldQty: p.quantity,
quantity: p.quantity));
});
});
}
@override
Widget build(BuildContext context) {
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
'inventory.take',
fontSize: 20,
color: Colors.white,
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send),
onPressed: () {
showConfirmDialog(context, "inventory.confirm", () {
_submit();
});
},
)
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: () async {
final InventoryLine inventoryLine =
await Navigator.push<InventoryLine>(
context,
MaterialPageRoute(builder: (context) => InventoryItem()),
);
_save(inventoryLine);
},
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
columns: [
MyDataColumn(
label: LocalText(context, "inventory.product")),
MyDataColumn(
label: LocalText(context, "inventory.storage")),
MyDataColumn(
label: LocalText(context, "inventory.old.qty"),
),
MyDataColumn(
label: LocalText(context, "inventory.new.qty"),
),
],
rows: getInventoryTable(inventoryTaking.inventoryLines),
),
),
)),
);
}
List<MyDataRow> getInventoryTable(List<InventoryLine> invLines) {
return invLines.map((p) {
return MyDataRow(
onSelectChanged: (bool selected) async {
var inventoryLine = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InventoryItem(
inventoryLine: p,
)),
);
_save(inventoryLine);
},
cells: [
MyDataCell(
new Text(
p.productName,
style: textStyle,
),
),
MyDataCell(
new Text(p.storageName.toString(), style: textStyle),
),
MyDataCell(
new Text(p.oldQty.toString(), style: textStyle),
),
MyDataCell(
new Text(p.quantity.toString(), style: textStyle),
),
],
);
}).toList();
}
_save(InventoryLine inventoryLine) {
if (inventoryLine == null) return;
if (inventoryLine.action == "create") {
if (inventoryTaking.inventoryLines.contains(inventoryLine)) {
showMsgDialog(context, "Error", "Duplicate line");
return;
}
inventoryTaking.inventoryLines.add(inventoryLine);
} else if (inventoryLine.action == "delete") {
inventoryTaking.inventoryLines.remove(inventoryLine);
}
}
_submit() async {
if (inventoryTaking.inventoryLines.length == 0) {
showMsgDialog(context, "Error", "No inventory line");
return;
}
setState(() {
_isLoading = true;
});
try {
StorageModel storageModel = Provider.of<StorageModel>(context);
await storageModel.createInventoryTaking(inventoryTaking);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,212 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/storage_model.dart';
import 'package:fcs/pages/storage/inventory_take.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/inventory_taking.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/progress.dart';
class InventoryTakingList extends StatefulWidget {
@override
_InventoryTakingListState createState() => _InventoryTakingListState();
}
class _InventoryTakingListState extends State<InventoryTakingList> {
DateTime _selectedDate = DateTime.now();
int _selectedIndex = 0;
bool _isLoading = false;
@override
void initState() {
super.initState();
var storageModel = Provider.of<StorageModel>(context, listen: false);
_selectedIndex = storageModel.selectedIndex;
_selectedDate = storageModel.selectedDate;
}
@override
void dispose() {
super.dispose();
}
Future<Null> _selectDate(BuildContext context) async {
StorageModel storageModel = Provider.of<StorageModel>(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._selectedIndex = pickedDate == currentDate ? 0 : 1;
setState(() {
_selectedDate = picked;
storageModel.filterDate(_selectedDate, _selectedIndex);
});
}
}
@override
Widget build(BuildContext context) {
StorageModel storageModel = Provider.of<StorageModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("inventory.takings"),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle(fontSize: 18)
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
actions: <Widget>[
InkWell(
child: Container(
padding: EdgeInsets.only(right: 15, top: 20),
child: Stack(
children: <Widget>[
Image.asset(
"assets/date_filter.png",
color: Colors.white,
width: 25,
),
_selectedIndex == 0
? Container()
: Positioned(
bottom: 15,
right: 10,
child: Container(
width: 10,
height: 10,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: secondaryColor,
),
),
)
],
),
),
onTap: () => _selectDate(context),
),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TakeInventory()),
);
},
),
body: new ListView.builder(
padding: EdgeInsets.only(left: 10, right: 10, top: 15),
shrinkWrap: true,
itemCount: storageModel.inventoryTakings.length,
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 8,
child: ExpansionTile(
onExpansionChanged: (e) => _onExpend(
context, e, storageModel.inventoryTakings[index]),
title: ListTile(
leading: Material(
color: primaryColor,
shape: CircleBorder(),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Icon(Icons.note, color: Colors.white))),
title: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(storageModel.inventoryTakings[index].userName,
style: textStyle),
),
subtitle: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
DateFormat('dd MMM yyyy hh:mm a').format(
storageModel.inventoryTakings[index].dateTime),
style: subTitleStyle),
),
),
children: <Widget>[
storageModel.inventoryTakings[index].linesLoaded
? Container(
padding: EdgeInsets.only(top: 10),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
headingRowHeight: 40,
columnSpacing: 50,
columns: [
MyDataColumn(
label: LocalText(
context, "inventory.product")),
MyDataColumn(
label: LocalText(
context, "inventory.storage")),
MyDataColumn(
label: LocalText(
context, "inventory.quantity")),
],
rows: getInventoryTable(
storageModel.inventoryTakings[index]),
),
),
)
: Container(child: Text("Loading..."))
],
),
);
}),
),
);
}
List<MyDataRow> getInventoryTable(InventoryTaking inventoryTaking) {
return inventoryTaking.inventoryLines.map((p) {
return MyDataRow(
cells: [
MyDataCell(
new Text(
p.productName,
style: textStyle,
),
),
MyDataCell(
new Text(p.storageName.toString(), style: textStyle),
),
MyDataCell(
new Text(p.quantity.toString(), style: textStyle),
),
],
);
}).toList();
}
_onExpend(BuildContext context, expended, InventoryTaking inventoryTaking) {
if (!expended) return;
StorageModel storageModel = Provider.of<StorageModel>(context);
storageModel.loadInventoryLines(inventoryTaking);
}
}

View File

@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/storage_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/storage.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/progress.dart';
class StorageAddition extends StatefulWidget {
final Storage storage;
const StorageAddition({Key key, this.storage}) : super(key: key);
@override
_StorageAdditionState createState() => _StorageAdditionState();
}
class _StorageAdditionState extends State<StorageAddition> {
final _formKey = GlobalKey<FormState>();
TextEditingController nameController = new TextEditingController();
bool _isLoading = false;
@override
void initState() {
super.initState();
if (widget.storage != null) {
nameController.text = widget.storage.name;
}
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(context, "storage.item.title",color: Colors.white,fontSize: 20,),
actions: <Widget>[
widget.storage == null
? Container()
: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
showConfirmDialog(context, "storage.delete_confirm", () {
_delete(context);
});
},
),
IconButton(
icon: Icon(Icons.save),
onPressed: () {
_save(context);
},
)
],
),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 10),
child: TextFormField(
controller: nameController,
autofocus: false,
cursorColor: primaryColor,
decoration: new InputDecoration(
labelText: AppTranslations.of(context)
.text("storage.name"),
labelStyle:
languageModel.isEng ? labelStyle : labelStyleMM,
icon: Image.asset(
"assets/inventory.png",
color: primaryColor,
width: 25,
),
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("storage.form.name");
}
return null;
},
),
)
],
),
),
],
),
)),
);
}
void _save(BuildContext context) async {
if (!_formKey.currentState.validate()) return;
setState(() {
_isLoading = true;
});
try {
if (widget.storage != null) {
widget.storage.name = nameController.text;
await Provider.of<StorageModel>(context, listen: false)
.updateStorage(widget.storage);
} else {
await Provider.of<StorageModel>(context, listen: false)
.createStorage(Storage(name: nameController.text));
}
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
void _delete(BuildContext context) async {
setState(() {
_isLoading = true;
});
try {
if (widget.storage != null) {
await Provider.of<StorageModel>(context, listen: false)
.deleteStorage(widget.storage.id);
}
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/storage_model.dart';
import 'package:fcs/pages/storage/storage_addition.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/popup_menu.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/popupmenu.dart';
import 'package:fcs/widget/progress.dart';
import 'inventory_taking_list.dart';
class StorageList extends StatefulWidget {
@override
_StorageListState createState() => _StorageListState();
}
class _StorageListState extends State<StorageList> {
final double dotSize = 15.0;
PopupMenu selectedChoices = storageMenus[0];
bool _isLoading = false;
@override
Widget build(BuildContext context) {
var storageModel = Provider.of<StorageModel>(context);
void _select(PopupMenu choice) async {
selectedChoices = choice;
if (choice.status == "Inventory Takings") {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => InventoryTakingList()),
);
}
}
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(
context,
"storage.title",
color: Colors.white,
fontSize: 20,
),
actions: <Widget>[
PopupMenuButton<PopupMenu>(
elevation: 3.2,
onSelected: _select,
itemBuilder: (BuildContext context) {
return storageMenus.map((PopupMenu choice) {
return PopupMenuItem<PopupMenu>(
value: choice,
child: Text(choice.status),
);
}).toList();
})
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: primaryColor,
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => StorageAddition()),
);
},
),
body: new ListView.builder(
padding: EdgeInsets.only(left: 10, right: 10, top: 15),
shrinkWrap: true,
itemCount: storageModel.storages.length,
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 10,
color: Colors.white,
child: Theme(
data: ThemeData(accentColor: Colors.grey),
child: ExpansionTile(
onExpansionChanged: (e) =>
_onExpend(context, e, storageModel.storages[index]),
title: Row(
children: <Widget>[
new Row(
children: <Widget>[
new Padding(
padding: new EdgeInsets.symmetric(
horizontal: 10.0 - dotSize / 2),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Image.asset(
"assets/inventory.png",
width: 40,
color: primaryColor,
)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
storageModel.storages[index].name == null
? ""
: storageModel.storages[index].name,
style: textStyle),
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StorageAddition(
storage:
storageModel.storages[index])),
);
},
),
),
],
),
SizedBox(
height: 15,
)
],
),
children: <Widget>[
storageModel.storages[index].productsLoaded
? Container(
padding: EdgeInsets.only(top: 10),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
headingRowHeight: 40,
columnSpacing: 50,
columns: [
MyDataColumn(
label: LocalText(
context, "inventory.product"),
),
MyDataColumn(
label: LocalText(
context, "inventory.quantity"),
),
],
rows: getProductRow(
storageModel.storages[index]),
),
),
)
: Text("Loading..."),
],
),
),
);
}),
),
);
}
List<MyDataRow> getProductRow(Storage storage) {
return storage.products.map((p) {
return MyDataRow(
cells: [
MyDataCell(
new Text(
p.name,
style: textStyle,
),
),
MyDataCell(
NumberCell(p.quantity)
),
],
);
}).toList();
}
_onExpend(BuildContext context, expended, Storage storage) {
if (!expended) return;
storage.productsLoaded = false;
StorageModel storageModel = Provider.of<StorageModel>(context);
storageModel.loadProducts(storage);
}
}

151
lib/pages/term.dart Normal file
View File

@@ -0,0 +1,151 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/pages/term_edit.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/progress.dart';
import 'package:zefyr/zefyr.dart';
import '../theme/theme.dart';
typedef void ProfileCallback();
class Term extends StatefulWidget {
final bool agreePage;
const Term({Key key, this.agreePage = false}) : super(key: key);
@override
_TermState createState() => _TermState();
}
class _TermState extends State<Term> {
ZefyrController _controller;
FocusNode _focusNode;
NotusDocument document = new NotusDocument();
bool isLoading = false;
@override
void initState() {
super.initState();
_controller = ZefyrController(_loadDocument());
_focusNode = FocusNode();
}
NotusDocument _loadDocument() {
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
String term = mainModel.setting.terms;
NotusDocument doc;
try {
doc = NotusDocument.fromJson(jsonDecode(term));
} 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();
bool aggreed = mainModel.user.agreeTerms;
final agreeBtn = Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Card(
elevation: 10,
child: Container(
height: 45.0,
child: ButtonTheme(
minWidth: 900.0,
height: 100.0,
child: FlatButton.icon(
onPressed: () {
showConfirmDialog(context, "term.iagree", () async {
_agree();
});
},
label: LocalText(context, "term.agree_btn"),
icon: Icon(
Icons.check,
),
),
),
),
));
return LocalProgress(
inAsyncCall: isLoading,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: !widget.agreePage,
title: LocalText(context, 'term.title',
color: Colors.white, fontSize: 20),
backgroundColor: primaryColor,
actions: <Widget>[
isOwnerAndAbove || hasAdmin
? IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TermEdit()),
);
},
)
: Container()
],
),
body: Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: ZefyrTheme(
data: ZefyrThemeData().copyWith(
),
child: ZefyrScaffold(
child: ZefyrEditor(
mode: ZefyrMode.view,
padding: EdgeInsets.all(16),
controller: _controller,
focusNode: _focusNode,
),
))),
)),
!aggreed ? agreeBtn : Container(),
],
),
),
);
}
_agree() async {
setState(() {
isLoading = true;
});
try {
MainModel mainModel = Provider.of<MainModel>(context);
await mainModel.agreeTerms();
if (widget.agreePage) {
Future.delayed(const Duration(milliseconds: 3000), () {
Navigator.pop(context);
});
}
} catch (e) {
showMsgDialog(context, "Error", e.toString());
setState(() {
isLoading = false;
});
}
}
}

123
lib/pages/term_edit.dart Normal file
View File

@@ -0,0 +1,123 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:provider/provider.dart';
import 'package:quill_delta/quill_delta.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/pages/util.dart';
import 'package:fcs/widget/progress.dart';
import 'package:zefyr/zefyr.dart';
import '../theme/theme.dart';
typedef void ProfileCallback();
class TermEdit extends StatefulWidget {
@override
_TermEditState createState() => _TermEditState();
}
class _TermEditState extends State<TermEdit> {
/// Allows to control the editor and the document.
ZefyrController _controller;
/// Zefyr editor like any other input field requires a focus node.
FocusNode _focusNode;
bool _isLoading;
@override
void initState() {
super.initState();
_isLoading = false;
// Here we must load the document and pass it to Zefyr controller.
final document = _loadDocument();
_controller = ZefyrController(document);
_focusNode = FocusNode();
}
/// Loads the document to be edited in Zefyr.
NotusDocument _loadDocument() {
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
String term = mainModel.setting.terms;
NotusDocument doc;
try {
doc = NotusDocument.fromJson(jsonDecode(term));
} catch (e) {}
if (doc == null) {
doc = NotusDocument();
}
return doc;
}
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
var singleChildScrollView = SingleChildScrollView(
padding: EdgeInsets.only(
left: 25.0,
right: 25.0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 300,
height: 300,
child: Card(child: Markdown(data: mainModel.setting.terms))),
],
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
title: Text(
"Terms",
),
backgroundColor: primaryColor,
actions: <Widget>[
IconButton(
icon: Icon(Icons.save),
onPressed: () {
_save();
},
)
],
),
body: ZefyrScaffold(
child: ZefyrTheme(
data: ZefyrThemeData().copyWith(
),
child: ZefyrEditor(
padding: EdgeInsets.all(16),
controller: _controller,
focusNode: _focusNode,
),
),
),
),
);
}
_save() {
setState(() {
_isLoading = true;
});
try {
final contents = jsonEncode(_controller.document);
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
mainModel.updateTerms(contents);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
_isLoading = false;
Navigator.pop(context);
}
}
}

157
lib/pages/test_list.dart Normal file
View File

@@ -0,0 +1,157 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/test_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/popup_menu.dart';
class TestList extends StatefulWidget {
@override
_TestListState createState() => _TestListState();
}
class _TestListState extends State<TestList> {
bool isLoading = false;
@override
void initState() {
super.initState();
var testModel = Provider.of<TestModel>(context, listen: false);
testModel.initData();
print("List initState!");
}
@override
void dispose() {
print("List Dispose!");
super.dispose();
}
@override
Widget build(BuildContext context) {
var testModel = Provider.of<TestModel>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text("Tests"),
actions: <Widget>[
PopupMenuButton<PopupMenu>(
elevation: 3.2,
onSelected: (s) {
if (s.index == 0) Provider.of<TestModel>(context).populate();
if (s.index == 1) Provider.of<TestModel>(context).add();
if (s.index == 2) Provider.of<TestModel>(context).update();
if (s.index == 3) Provider.of<TestModel>(context).remove();
},
itemBuilder: (BuildContext context) {
return [
PopupMenuItem<PopupMenu>(
value: PopupMenu(status: "Populate", index: 0),
child: Text("Populate"),
),
PopupMenuItem<PopupMenu>(
value: PopupMenu(status: "Add", index: 1),
child: Text("Add"),
),
PopupMenuItem<PopupMenu>(
value: PopupMenu(status: "Update", index: 2),
child: Text("Update"),
),
PopupMenuItem<PopupMenu>(
value: PopupMenu(status: "Delete", index: 3),
child: Text("Delete"),
)
];
})
],
),
body: Column(
children: <Widget>[
Expanded(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (!isLoading &&
scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent &&
!testModel.ended) {
// start loading data
_loadData(testModel);
}
return null;
},
child: ListView.builder(
padding: EdgeInsets.only(left: 15, right: 15, top: 15),
shrinkWrap: true,
itemCount: testModel.ended
? testModel.tests.length + 1
: testModel.tests.length,
itemBuilder: (BuildContext context, int index) {
return (testModel.ended && index == testModel.tests.length)
? Center(child: Text("No more item"))
: buildCard(testModel.tests[index], index + 1);
}),
),
),
Container(
height: isLoading ? 50.0 : 0,
color: Colors.transparent,
child: Center(
child: new CircularProgressIndicator(),
),
),
],
),
);
}
Card buildCard(Test test, int index) {
return Card(
elevation: 10,
color: Colors.white,
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.account_circle,
color: primaryColor,
size: 55,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(
"$index - ${test.name}",
style: new TextStyle(fontSize: 17.0, color: Colors.black),
),
new Text(
test.age.toString(),
style: new TextStyle(fontSize: 12.0, color: Colors.black),
),
],
),
],
),
),
);
}
_loadData(TestModel testModel) {
setState(() {
isLoading = true;
});
Timer(Duration(seconds: 1), () {
testModel.load();
setState(() {
isLoading = false;
});
});
}
}

276
lib/pages/user_editor.dart Normal file
View File

@@ -0,0 +1,276 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/model/user_model.dart';
import 'package:fcs/pages/util.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/progress.dart';
class UserEditor extends StatefulWidget {
final User user;
final bool viewOnly;
const UserEditor({this.user, this.viewOnly = false});
@override
_UserEditorState createState() => _UserEditorState();
}
class _UserEditorState extends State<UserEditor> {
TextEditingController _name = new TextEditingController();
TextEditingController _phone = new TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
String currentLevelId;
List<Privilege> privileges = [];
User _user = new User();
@override
void initState() {
super.initState();
var userModel = Provider.of<UserModel>(context, listen: false);
privileges = Provider.of<UserModel>(context, listen: false).getPrivileges;
if (widget.user != null) {
userModel.getUserList().forEach((u) {
if (widget.user.docID == u.docID) {
_user = u;
}
});
_name.text = _user.name;
_phone.text = _user.phone;
this.currentLevelId = _user.userLevelID;
privileges.forEach((p) => _user.privilegeIds.contains(p.id)
? p.isChecked = true
: p.isChecked = false);
}
}
Widget showUserLevelList(BuildContext context, UserModel userModel) {
return Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(
Icons.security,
color: primaryColor,
),
SizedBox(
width: 20,
),
new Flexible(
child: Container(
width: 150.0,
child: DropdownButton<String>(
value: currentLevelId,
isExpanded: true,
hint: Text(
'Select User Level',
),
onChanged: changedDropDownItem,
items: userModel.userLevels
.map<DropdownMenuItem<String>>((UserLevel userLevel) {
return new DropdownMenuItem<String>(
value: userLevel.id,
child: new Text(userLevel.name,
style:
new TextStyle(color: Colors.black87, fontSize: 17)),
);
}).toList(),
),
),
),
],
);
}
void changedDropDownItem(selected) {
setState(() {
currentLevelId = selected;
});
}
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]),
),
),
],
),
],
));
}),
);
}
@override
Widget build(BuildContext context) {
var userModel = Provider.of<UserModel>(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 phoneNoBox = TextFormField(
controller: _phone,
autofocus: false,
readOnly: true,
cursorColor: primaryColor,
decoration: new InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
icon: Icon(
Icons.phone,
color: primaryColor,
),
),
);
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(context);
},
child: LocalText(
context,
'user.save',
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(context, 'user.title',
color: Colors.white, fontSize: 20),
),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
namebox,
Row(
children: <Widget>[
Expanded(child: phoneNoBox),
Expanded(
child: InkWell(
onTap: () => call(context, _phone.text),
child: Icon(
Icons.open_in_new,
color: Colors.grey,
size: 15,
),
),
),
],
),
widget.viewOnly
? Container()
: showUserLevelList(context, userModel),
widget.viewOnly
? Container()
: showprivilegeList(context, userModel),
],
),
),
SizedBox(
height: 15,
),
widget.viewOnly ? Container() : saveButton,
SizedBox(
height: 20,
)
],
),
)),
);
}
List<String> privilegesIDs() {
return this.privileges.where((p) => p.isChecked).map((p) => p.id).toList();
}
_save(BuildContext context) async {
setState(() {
_isLoading = true;
});
try {
var userModel = Provider.of<UserModel>(context);
await userModel.addLevel(
_phone.text, this.currentLevelId, privilegesIDs());
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
}
setState(() {
_isLoading = false;
});
}
}

260
lib/pages/user_list.dart Normal file
View File

@@ -0,0 +1,260 @@
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';
import 'user_editor.dart';
import 'package:intl/intl.dart';
import 'user_search_page.dart';
class UserList extends StatefulWidget {
@override
_UserListState createState() => _UserListState();
}
class _UserListState extends State<UserList> {
var dateFormatter = new DateFormat('dd MMM yyyy - hh:mm:ss a');
final double dotSize = 15.0;
bool _isLoading = false;
PopupMenu selectedChoices = userpopup[0];
User user = new User();
int _selectedIndex;
@override
void initState() {
var userModel = Provider.of<UserModel>(context, listen: false);
var index = userModel.popupMenu.index;
_selectedIndex = index;
super.initState();
}
@override
Widget build(BuildContext context) {
var userModel = Provider.of<UserModel>(context);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: LocalText(context, 'users.title',
color: Colors.white, fontSize: 20),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: Colors.white,
),
iconSize: 30,
onPressed: () => showUserPlacesSearch(context),
),
PopupMenuButton<PopupMenu>(
elevation: 3.2,
onSelected: (selected) {
setState(() {
this._selectedIndex = selected.index;
});
userModel.filterSorting(_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,
),
_selectedIndex != 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,
),
_selectedIndex != null &&
_selectedIndex == choice.index
? Icon(
Icons.check,
color: Colors.grey,
)
: Container(),
],
),
),
);
}).toList();
}),
],
),
body: new ListView.builder(
padding: EdgeInsets.only(left: 15, right: 15, top: 15, bottom: 10),
shrinkWrap: true,
itemCount: userModel.getUserList().length,
itemBuilder: (BuildContext context, int index) {
User _u = userModel.getUserList()[index];
return Card(
elevation: 10,
color: Colors.white,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserEditor(user: _u)),
);
},
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: 20.0 - dotSize / 2),
child: Icon(
Icons.account_circle,
color: primaryColor,
size: 55,
),
),
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
(_u.name == null ? "" : _u.name) +
(_u.isBlock ? " (Blocked)" : ""),
style: new TextStyle(
fontSize: 16.0, color: Colors.black),
),
new Text(
_u.phone == null ? "" : _u.phone,
style: new TextStyle(
fontSize: 14.0, color: Colors.grey),
),
_u.device == null
? Text("No login",
style: TextStyle(color: Colors.red))
: Text("last active",
style:
TextStyle(color: Colors.green)),
Text(
"${_u.device == null ? "" : _u.device}",
style: TextStyle(fontSize: 11)),
Text(
"${_u.lastActiveTime == null ? "" : dateFormatter.format(_u.lastActiveTime)}")
],
),
),
],
),
),
),
PopupMenuButton<PopupMenu>(
elevation: 3.2,
tooltip: 'This is tooltip',
onSelected: _select,
itemBuilder: (BuildContext context) {
this.user = _u;
return userpopup.map((PopupMenu choice) {
return PopupMenuItem<PopupMenu>(
enabled: choice.index == 1
? _u.isBlockUser() ? false : true
: _u.isBlockUser() ? true : false,
value: choice,
child: Text(choice.status),
);
}).toList();
})
],
),
),
);
}),
),
);
}
void _select(PopupMenu choice) async {
selectedChoices = choice;
if (choice.index == 1) {
showConfirmDialog(context, "user.block.confirm", () {
_block();
});
} else if (choice.index == 2) {
showConfirmDialog(context, "user.unblock.confirm", () {
_unblock();
});
}
}
_block() async {
setState(() {
_isLoading = true;
});
try {
var userModel = Provider.of<UserModel>(context);
await userModel.blockPhone(this.user.phoneNumber);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_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;
});
}
}
}

View File

@@ -0,0 +1,189 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:fcs/model/user_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/user.dart';
import 'user_editor.dart';
Future<User> showUserPlacesSearch(BuildContext context) async =>
await showSearch<User>(
context: context,
delegate: UserSearchDelegate(),
);
class UserSearchDelegate extends SearchDelegate<User> {
@override
ThemeData appBarTheme(BuildContext context) {
final ThemeData theme = Theme.of(context);
return theme.copyWith(
inputDecorationTheme: InputDecorationTheme(
hintStyle: TextStyle(
color: theme.primaryTextTheme.title.color, fontSize: 16)),
primaryColor: primaryColor,
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.white),
primaryColorBrightness: Brightness.light,
primaryTextTheme: theme.textTheme,
textTheme: theme.textTheme.copyWith(
title: theme.textTheme.title.copyWith(
color: theme.primaryTextTheme.title.color, fontSize: 16)),
);
}
@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () => query = '',
),
];
}
@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => close(context, null),
);
}
@override
Widget buildResults(BuildContext context) {
final userModel = Provider.of<UserModel>(context);
return FutureBuilder(
future: userModel.searchUser(query),
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasData) {
if (snapshot.data.length == 0) {
return Container(
child: Center(
child: Text(
"Error :No Search User",
textAlign: TextAlign.center,
),
),
);
}
return Container(
padding: EdgeInsets.only(top: 15),
child: ListView(
children: snapshot.data.map((u) => UserRow(user: u)).toList(),
),
);
} else if (snapshot.hasError) {
return Container(
child: Center(
child: Text(
'${snapshot.error}',
textAlign: TextAlign.center,
),
),
);
} else {
return Container(
child: Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(primaryColor)),
),
);
}
});
}
@override
Widget buildSuggestions(BuildContext context) {
return Container(
child: Center(
child: Opacity(
opacity: 0.2,
child: Icon(
Icons.supervised_user_circle,
size: 200,
)),
),
);
}
}
class UserRow extends StatefulWidget {
final User user;
const UserRow({Key key, this.user}) : super(key: key);
@override
_UserRowState createState() => _UserRowState();
}
class _UserRowState extends State<UserRow> {
var dateFormatter = new DateFormat('dd MMM yyyy - hh:mm:ss a');
@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) => UserEditor(user: widget.user)),
);
},
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: 20.0 - 15 / 2),
child: Icon(
Icons.account_circle,
color: primaryColor,
size: 55,
),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
widget.user.name == null ? "" : widget.user.name,
style: new TextStyle(
fontSize: 16.0, color: Colors.black),
),
new Text(
widget.user.phone == null ? "" : widget.user.phone,
style: new TextStyle(
fontSize: 14.0, color: Colors.grey),
),
widget.user.device == null
? Text("No login",
style: TextStyle(color: Colors.red))
: Text("last active",
style: TextStyle(color: Colors.green)),
Text(
"${widget.user.device == null ? "" : widget.user.device}",
style: TextStyle(fontSize: 11)),
Text(
"${widget.user.lastActiveTime == null ? "" : dateFormatter.format(widget.user.lastActiveTime)}")
],
),
],
),
),
),
],
),
),
),
);
}
}

317
lib/pages/util.dart Normal file
View File

@@ -0,0 +1,317 @@
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fcs/model/announcement_model.dart';
import 'package:fcs/model/buyer_model.dart';
import 'package:fcs/model/do_model.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/notification_model.dart';
import 'package:fcs/model/po_model.dart';
import 'package:fcs/model/user_model.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/buyer.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/vo/notification.dart' as Noti;
import 'announcement.dart';
import 'buyer_info.dart';
import 'do/do_approve.dart';
import 'log_list.dart';
import 'my_registeration_info.dart';
import 'po/po_submission_form.dart';
import 'products_list.dart';
import 'user_editor.dart';
final log = Logger('Util');
void showMsgDialog(BuildContext context, String title, String msg) {
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: new Text(title),
content: new Text(msg),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
var selectedColor;
void showColorPicker(BuildContext context, Color color, callback(Color color)) {
showDialog(
context: context,
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0))),
title: const Text('Pick product color'),
content: SingleChildScrollView(
child: ColorPicker(
pickerColor: color.value == 0 ? Colors.red : color,
pickerAreaHeightPercent: 0.6,
onColorChanged: (Color value) {
selectedColor = value;
},
),
),
actions: <Widget>[
FlatButton(
child: const Text('Choose'),
onPressed: () {
callback(selectedColor == null ? Colors.red : selectedColor);
Navigator.of(context).pop();
},
),
FlatButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
);
}
Future<void> showConfirmDialog(
BuildContext context, String translationKey, ok(),
{List<String> translationVariables}) async {
await showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: Center(
child: LocalText(
context,
translationKey,
translationVariables: translationVariables,
),
),
content: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text(
AppTranslations.of(context).text('Cancel'),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle()
: TextStyle(fontFamily: 'MyanmarUnicode'),
),
onPressed: () {
Navigator.of(context).pop();
}),
FlatButton(
color: primaryColor,
child: Text(AppTranslations.of(context).text('Ok'),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold)
: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontFamily: 'MyanmarUnicode')),
onPressed: () async {
Navigator.of(context).pop();
await ok();
})
],
),
),
);
});
}
void showCommentDialog(BuildContext context, commentCallback(comment)) {
TextEditingController _comment = new TextEditingController();
showDialog(
context: context,
builder: (_) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0))),
content: Container(
width: 300.0,
height: 80.0,
child: Container(
child: TextFormField(
controller: _comment,
autofocus: false,
cursorColor: primaryColor,
maxLines: 3,
style: textStyle,
decoration: new InputDecoration(
labelText: "Comment",
labelStyle: labelStyle,
icon: Icon(
Icons.add_comment,
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 comment";
}
return null;
},
),
),
),
actions: <Widget>[
FlatButton(
child: Text(
"Cancel",
style: labelStyle,
),
onPressed: () {
_comment.clear();
Navigator.of(context).pop();
}),
FlatButton(
color: primaryColor,
child: Text("Submit",
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold)),
onPressed: () {
commentCallback(_comment.text);
Navigator.of(context).pop();
})
],
);
});
}
Widget getStatus(String status) {
return status == "approved"
? Chip(
backgroundColor: Colors.green,
avatar: Icon(
Icons.check,
color: Colors.white,
size: 14,
),
label: Text(
status,
style: TextStyle(color: Colors.white, fontSize: 12),
))
: status == "rejected"
? Chip(
backgroundColor: Colors.red,
avatar: Icon(
Icons.remove,
color: Colors.white,
size: 14,
),
label: Text(
status,
style: TextStyle(color: Colors.white, fontSize: 12),
))
: status == "pending"
? Chip(
backgroundColor: Colors.blue,
avatar: Icon(
Icons.timelapse,
color: Colors.white,
size: 14,
),
label: Text(
status,
style: TextStyle(color: Colors.white, fontSize: 12),
))
: Chip(
avatar: Icon(
Icons.check,
size: 14,
),
label: Text(status));
}
call(BuildContext context, String phone) {
showConfirmDialog(context, "contact.phone.confim", () => launch("tel:$phone"),
translationVariables: ["$phone"]);
}
Future<void> displayNotiContent(
BuildContext context, Noti.Notification noti) async {
if (!noti.seen) {
Provider.of<NotificationModel>(context, listen: false).seenID(noti.id);
}
try {
if (noti.itemType == "buyer") {
BuyerModel buyerModel = Provider.of<BuyerModel>(context, listen: false);
Buyer buyer = await buyerModel.getBuyer(noti.itemID);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BuyerInfo(
buyer: buyer,
)),
);
} else if (noti.itemType == "announcement") {
AnnouncementModel announcementModel =
Provider.of<AnnouncementModel>(context, listen: false);
var announce = await announcementModel.getAnnouncement(noti.itemID);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AnnouncementPage(announcement: announce)),
);
} else if (noti.itemType == "reg") {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyRegisterationInfo()),
);
} else if (noti.itemType == "po") {
POSubmissionModel poModel =
Provider.of<POSubmissionModel>(context, listen: false);
var po = await poModel.getPO(noti.itemID);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => POSubmissionForm(
poSubmission: po,
)),
);
} else if (noti.itemType == "do") {
DOModel doModel = Provider.of<DOModel>(context, listen: false);
var _do = await doModel.getDO(noti.itemID);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DOApproval(doSubmission: _do)),
);
} else if (noti.itemType == "price") {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => ProductsList()));
} else if (noti.itemType == 'new_device_login') {
Navigator.of(context).push(MaterialPageRoute(builder: (_) => LogList()));
} else if (noti.itemType == 'user') {
UserModel userModel = Provider.of<UserModel>(context, listen: false);
var user = await userModel.getUser(noti.itemID);
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => UserEditor(
user: user,
viewOnly: true,
)));
}
} catch (e) {
log.warning("Error:$e \n ${noti.toString()}");
showMsgDialog(context, "Error", "Notification item not found!");
} finally {}
}

161
lib/pages/welcome_page.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:fcs/model/language_model.dart';
import 'package:fcs/widget/banner.dart';
import 'package:fcs/widget/localization/transalation.dart';
import 'package:fcs/widget/offline_redirect.dart';
import 'package:fcs/widget/products.dart';
import 'contact.dart';
import 'login_page.dart';
import 'manual/manual_page.dart';
class WelcomePage extends StatefulWidget {
@override
_WelcomePageState createState() => _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage> {
String version = "";
List<bool> _selection = List.generate(2, (_) => false);
@override
void initState() {
super.initState();
_loadVersion();
var languageModel = Provider.of<LanguageModel>(context, listen: false);
languageModel.isEng ? _selection[0] = true : _selection[1] = true;
}
_loadVersion() async {
var version = await getVersionNumber();
setState(() {
this.version = version;
});
}
Future<String> getVersionNumber() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
return "v${packageInfo.version}+${packageInfo.buildNumber}";
}
@override
Widget build(BuildContext context) {
var languageModel = Provider.of<LanguageModel>(context);
var toggleButtons = ToggleButtons(
constraints: BoxConstraints(minWidth: 25.0, minHeight: 25.0),
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
decoration: languageModel.isEng
? BoxDecoration(
border: Border.all(color: Colors.blueAccent, width: 1))
: null,
child: Image.asset(
"assets/eng_flag.png",
width: 23,
),
),
),
Container(
decoration: !languageModel.isEng
? BoxDecoration(
border: Border.all(color: Colors.blueAccent, width: 1))
: null,
child: Image.asset(
"assets/myan_flag.png",
width: 23,
),
),
],
renderBorder: false,
isSelected: _selection,
fillColor: Colors.white,
onPressed: (int index) {
languageModel.saveLanguage(Translation().supportedLanguages[index]);
setState(() {
for (int buttonIndex = 0;
buttonIndex < _selection.length;
buttonIndex++) {
if (buttonIndex == index) {
_selection[buttonIndex] = true;
} else {
_selection[buttonIndex] = false;
}
}
});
},
);
return OfflineRedirect(
child: FlavorBanner(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(
color: Colors.grey,
),
elevation: 0,
title: Image(
height: 30,
fit: BoxFit.scaleDown,
image: new AssetImage('assets/img/logo.png')),
actions: <Widget>[
IconButton(
icon: Image.asset(
"assets/manual.png",
width: 30,
height: 30,
color: Colors.black,
),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => ManualPage())),
),
IconButton(
icon: Icon(
FontAwesomeIcons.phone,
color: Colors.black,
size: 20,
),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => Contact())),
),
IconButton(
icon: Icon(
FontAwesomeIcons.signInAlt,
color: Colors.black,
size: 20,
),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => LoginPage())),
),
],
),
body: ListView(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: toggleButtons,
),
],
),
Padding(
padding: const EdgeInsets.only(top: 5),
child: ProductsWidget(isWelcomePage: false),
),
SizedBox(height: 30,),
Center(child: Text("${this.version}")),
],
)),
),
);
}
}