backup. before shop update

This commit is contained in:
2021-08-31 13:28:33 -04:00
parent c378a6203c
commit 808ffa3211
292 changed files with 51551 additions and 695 deletions

View File

@@ -0,0 +1,83 @@
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
import '../../constants.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/store.dart';
class MobileBottomNav extends StatelessWidget {
final int currentIndex;
int count = 0;
MobileBottomNav({int currentIndex = 0}) : currentIndex = currentIndex;
@override
Widget build(BuildContext context) {
if (store.state.cartInfos != null && store.state.cartInfos.length > 0) {
count = 0;
for (var i = 0; i < store.state.cartInfos.length; i++) {
if (store.state.cartInfos[i].productList != null && store.state.cartInfos[i].productList.length > 0) {
count += 1;
}
}
} else {
count = 0;
}
return new BottomNavigationBar(
onTap: (index) => _go(context, index),
currentIndex: currentIndex,
fixedColor: Theme.of(context).primaryColor,
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.store),
label: S.of(context).home
),
new BottomNavigationBarItem(
icon: count > 0 ? Badge(
badgeContent: Text('${count}', style: TextStyle(fontSize: 11.0, color: Colors.white),),
child: new Icon(Icons.shopping_basket),
) : Icon(Icons.shopping_basket),
label: S.of(context).shop
),
new BottomNavigationBarItem(
icon: new Icon(Icons.support_agent),
label: S.of(context).support
),
new BottomNavigationBarItem(
icon: new Icon(Icons.person),
label: S.of(context).me
)
]
);
}
_go(BuildContext context, int index) {
if (index == currentIndex) return;
String path = '';
switch(index) {
case 0:
path = '/';
break;
case 1:
path = '/shop';
break;
case 2:
path = '/my-support/${Constants.BUSINESS_ID}';
break;
case 3:
path = '/me';
break;
}
if (path.length > 0) {
print('go to: $path');
Routes.router.navigateTo(
context,
path,
replace: false,
);
}
}
}

View File

@@ -0,0 +1,287 @@
import 'package:flutter/material.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/product.dart';
import '../../models/product_attribute.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
import '../../widgets/general/attribute/check_options.dart';
import '../../widgets/general/attribute/qty_options.dart';
import '../../widgets/general/attribute/radio_options.dart';
class MobileAttributeSelection extends StatefulWidget {
final Product product;
final Business business;
final Key key;
final GlobalKey startKey;
const MobileAttributeSelection({@required this.product, @required this.business, this.key, this.startKey})
: super(key: key);
@override
State<StatefulWidget> createState() {
return new MobileAttributeSelectionState();
}
}
class MobileAttributeSelectionState extends State<MobileAttributeSelection> {
Product product;
int index;
FlatButton previousButton;
FlatButton nextButton;
bool previousButtonEnable;
bool nextButtonEnable;
String productDesc;
double productPrice;
String nextText;
String finishText;
Map<String, dynamic> selections = new Map();
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(S.of(context).select_options),
backgroundColor: Theme.of(context).primaryColor,
actions: [],
),
body: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_getProductContent(),
Expanded(
child: _getOptionsView(),
),
],
),
bottomNavigationBar: new Container(
padding: new EdgeInsets.all(10.0),
color: new Color(0xFFA8A8A8),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
previousButton,
nextButton
],
),
),
);
}
@override
void initState() {
super.initState();
setState(() {
index = 0;
product = widget.product;
previousButtonEnable = false;
nextButtonEnable = false;
productDesc = product.description;
productPrice = product.price;
});
eventBus.on<OnAttributeSelectionsChanged>().listen((event) {
this.selections = event.selections;
List<String> extendDescription = [];
double extendPrice = 0.0;
event.selections.forEach((key, value) {
List<String> opt = [];
for (var i = 0; i < (value as List).length; i++) {
if (value[i]['quantity'] == 0) {
extendPrice += value[i]['adjust_amount'];
opt.add(value[i]['name']);
} else {
extendPrice += value[i]['adjust_amount'] * value[i]['quantity'];
opt.add(value[i]['name'] + '*' + value[i]['quantity'].toString());
}
}
extendDescription.add(key + ': ' + opt.join(', '));
});
ProductAttribute pa = product.productAttributes[index];
if (pa.required && Utils.selectionsNotEmptyAt(selections, pa.name)) {
setState(() {
nextButtonEnable = true;
productDesc = product.description + ', ' + extendDescription.join('; ');
productPrice = product.price + extendPrice;
});
} else if (!pa.required){
setState(() {
nextButtonEnable = true;
productDesc = product.description + ', ' + extendDescription.join('; ');
productPrice = product.price + extendPrice;
});
} else {
setState(() {
nextButtonEnable = false;
productDesc = product.description + ', ' + extendDescription.join('; ');
productPrice = product.price + extendPrice;
});
}
});
}
bool _checkCanGoNext() {
ProductAttribute pa = product.productAttributes[index];
if (pa.required && Utils.selectionsNotEmptyAt(selections, pa.name)) {
return true;
} else if (!pa.required){
return true;
}
return false;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
nextText = S.of(context).next;
finishText = S.of(context).finish;
}
Widget _getProductContent() {
previousButton = new FlatButton(
onPressed: previousButtonEnable ? _goPrevious : null,
child: new Text(S.of(context).previous),
);
nextButton = new FlatButton(
onPressed: nextButtonEnable ? _goNext : null,
child: new Text(
product.productAttributes.length > index + 1 ? nextText : finishText
),
);
var productContent = new SizedBox(
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
padding: new EdgeInsets.all(10.0),
width: 70.0,
height: 70.0,
child: Util.showImage(product.imagePath,
width: 70.0,
height: 70.0,
fit: BoxFit.fill,
),
),
new Expanded(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
padding: new EdgeInsets.all(10.0).copyWith(left: 0.0).copyWith(bottom: 0.0),
child: new Text(
product.name,
style: new TextStyle(
fontSize: 15.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
new Container(
padding: new EdgeInsets.only(right: 10.0),
child: new Text(
productDesc,
style: new TextStyle(
fontSize: 9.0,
color: new Color(0xFFCDCDCD)
),
maxLines: 4,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
new Container(
padding: new EdgeInsets.all(10.0).copyWith(left: 0.0),
width: 70.0,
child:
new Text(
productPrice.toStringAsFixed(2),
textAlign: TextAlign.right,
style: new TextStyle(
fontSize: 18.0,
),
),
),
],
),
);
return productContent;
}
Widget _getOptionsView() {
Widget optionsView;
ProductAttribute productAttribute = product.productAttributes[index];
if (productAttribute.byQuantity) {
optionsView = new QtyOptions(product: product, index: index, selections: selections);
} else {
if (productAttribute.singleSelection) {
optionsView = new RadioOptions(product: product, index: index, selections: selections);
} else {
optionsView = new CheckOptions(product: product, index: index, selections: selections);
}
}
return optionsView;
}
void _goNext() {
if (index + 1 < product.productAttributes.length) {
setState(() {
index = index + 1;
previousButtonEnable = index >= 1;
nextButtonEnable = _checkCanGoNext();
});
} else if (product.productAttributes.length == index + 1) {
eventBus.fire(new OnProductWillAddToCart(product, selections, productPrice, productDesc, widget.business, buttonKey: widget.startKey));
Navigator.pop(context);
}
eventBus.fire(new OnAttributePageChanged(index));
}
void _goPrevious() {
if (index - 1 >= 0) {
setState(() {
index = index - 1;
previousButtonEnable = index > 0;
nextButtonEnable = _checkCanGoNext();
});
eventBus.fire(new OnAttributePageChanged(index));
}
}
@override
void setState(VoidCallback fn) {
if(mounted) {
super.setState(fn);
}
}
}

View File

@@ -0,0 +1,265 @@
import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/blog.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
import '../../widgets/mobile/MobileBottomNav.dart';
class MobileBlog extends StatefulWidget {
final int businessId;
const MobileBlog({Key key, this.businessId}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileBlogState();
}
}
class MobileBlogState extends State<MobileBlog> {
List<Blog> blogs;
int _page = 1;
int _pageCount = 1;
bool _isLoading = false;
bool _loadingFinish = false;
RefreshController _refreshController = RefreshController(initialRefresh: true);
void _onRefresh() {
_page = 1;
if (blogs != null) {
blogs.clear();
} else {
blogs = [];
}
_refreshController.resetNoData();
loadBlogs(true);
}
void _onLoadMore() {
// if failed,use loadFailed(),if no data return,use LoadNodata()
if (_pageCount > _page) {
_page += 1;
loadBlogs(false);
} else {
_refreshController.loadNoData();
}
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
BuildContext mainContext = context;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(S.of(context).blog),
backgroundColor: Theme.of(context).primaryColor,
actions: [],
),
body: SmartRefresher(
enablePullDown: true,
enablePullUp: true,
header: WaterDropHeader(),
footer: CustomFooter(
builder: (BuildContext context, LoadStatus mode){
Widget footer;
if(mode == LoadStatus.idle) {
footer = Text(S.of(context).pull_up_to_load_more);
} else if (mode == LoadStatus.loading) {
footer = CircularProgressIndicator();
} else if (mode == LoadStatus.failed) {
footer = Text(S.of(context).load_failed_retry);
} else if (mode == LoadStatus.canLoading) {
footer = Text(S.of(context).release_to_load_more);
} else if (mode == LoadStatus.noMore) {
footer = Text(S.of(context).no_more_record);
} else {
footer = Text('...');
}
return Container(
height: 55.0,
child: Center(child: footer,),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoadMore,
child: _buildBody(),
),
bottomNavigationBar: MobileBottomNav(currentIndex: 3,),
);
}
@override
void dispose() {
_refreshController?.dispose();
super.dispose();
}
Widget _buildBody() {
if (blogs == null) {
return SizedBox.shrink();
}
return ListView.builder(
itemCount: blogs.length <= 1 ? 1 : blogs.length,
itemBuilder: (BuildContext context, int i) {
if (blogs.length <= 0) {
return Container(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(S.of(context).no_blog_yet),
),
);
} else {
Blog blog = blogs[i];
Widget w = Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.6,
)
)
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(blog.thumbUrl != null) ?
Container(
padding: EdgeInsets.only(right: 10.0),
child: Util.showImage(
'https:${blog.thumbUrl}',
width: 80.0,
height: 80.0,
),
) : SizedBox.shrink(),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
blog.title,
style: TextStyle(
fontSize: 19.0,
),
overflow: TextOverflow.ellipsis,
),
),
Container(
child: Text(
Utils.utcDatetimeStringToLocalDatetimeString(context, blog.createdAt),
style: TextStyle(
fontSize: 13.0,
color: Colors.grey,
),
),
),
],
),
),
],
),
);
return GestureDetector(
child: w,
onTap: () {
Routes.router.navigateTo(context, '/view-blog/${blog.id}');
},
);
}
}
);
}
@override
void initState() {
super.initState();
eventBus.on<OnTicketsUpdated>().listen((event) {
if (mounted) {
setState(() {
blogs = null;
});
}
_refreshController.requestRefresh();
});
}
void loadBlogs(bool isRefresh) {
_loadingFinish = false;
HttpUtil.httpGet(
'v1/blogs',
businessId: widget.businessId,
queryParameters: {
'page': _page.toString(),
'size': Constants.BLOG_PER_PAGE_MOBILE.toString()
}
).then((value) {
if (mounted) {
if (isRefresh) {
_refreshController.refreshCompleted();
} else {
_refreshController.loadComplete();
}
if (int.parse(value['currentPage'].toString()) >= int.parse(value['pageCount'].toString())) {
_loadingFinish = true;
}
if (_loadingFinish) {
_refreshController.loadNoData();
}
_page = int.parse(value['currentPage'].toString());
_pageCount = int.parse(value['pageCount'].toString());
setState(() {
if (blogs == null) {
blogs = [];
}
blogs.addAll((value['blogs'] as List).map((e) => Blog.fromJson(e)).toList());
});
}
}).catchError((error) {
if (mounted) {
if (isRefresh) {
_refreshController.refreshFailed();
} else {
_refreshController.loadFailed();
}
_isLoading = false;
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
}
});
}
}

View File

@@ -0,0 +1,170 @@
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../models/key_value.dart';
import '../../routes.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileBuyService extends StatefulWidget {
final Map<String, dynamic> data;
const MobileBuyService(this.data, {Key key})
: super(key: key);
@override
State<StatefulWidget> createState() => MobileBuyServiceState();
}
class MobileBuyServiceState extends State<MobileBuyService> {
List<KeyValue> plans = [];
KeyValue selectedPlan;
double price = 0.0;
double tax = 0.0;
double paymentAmount = 0.0;
@override
Widget build(BuildContext context) {
Column col1 = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(bottom: 16),
child: Text(
S.of(context).purchase_renew_service,
style: TextStyle(
fontSize: 28,
),
),
),
],
);
col1.children.add(
Utils.buildLine(S.of(context).service_descritpion, widget.data['service_selections']['description'], valueSize: 16),
);
col1.children.add(
Utils.buildLine(S.of(context).your_group, widget.data['group']['name'], valueSize: 16),
);
if (widget.data['exists_service'] == null) {
col1.children.add(
Utils.buildLine(S.of(context).current_plan, 'N/A', valueSize: 16),
);
} else {
col1.children.add(
Utils.buildLine(S.of(context).current_plan, widget.data['exists_service']['description'], valueSize: 16),
);
col1.children.add(
Utils.buildLine(S.of(context).expiration_date,
Utils.utcDatetimeStringToLocalDatetimeString(context,
widget.data['exists_service']['expiration_date']),
valueSize: 16
),
);
}
col1.children.add(
Utils.buildLine(S.of(context).select_a_plan, DropdownButton<KeyValue>(
items: plans.map((e) {
return DropdownMenuItem<KeyValue>(
value: e,
child: Text(e.name),
);
}).toList(),
isExpanded: true,
hint: Text(
S.of(context).select_a_plan,
),
onChanged: (newValue) {
print('newValue $newValue');
setState(() {
selectedPlan = newValue;
price = selectedPlan.value['price'];
tax = selectedPlan.value['price'] * selectedPlan.value['tax'];
paymentAmount = price + tax;
});
},
value: selectedPlan,
), valueSize: 16),
);
col1.children.add(
Utils.buildLine(S.of(context).price, price.toStringAsFixed(2), valueSize: 16)
);
col1.children.add(
Utils.buildLine(S.of(context).tax, tax.toStringAsFixed(2), valueSize: 16)
);
col1.children.add(
Utils.buildLine(S.of(context).total, paymentAmount.toStringAsFixed(2), valueSize: 32)
);
col1.children.add(
Container(
padding: EdgeInsets.only(left: 0, right: 0, top: 16),
alignment: Alignment.centerRight,
child: ElevatedButton(
child: Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
child: Text(
S.of(context).pay_now,
style: TextStyle(
fontSize: 20,
),
),
),
onPressed: (paymentAmount > 0) ? () => _submit() : null,
),
),
);
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).purchase_renew_service),
backgroundColor: Theme.of(context).primaryColor,
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
child: col1,
),
),
);
}
void _submit() {
if (store.state.user == null) {
Utils.requireLogin(context, returnUrl: '/buy-service/${widget.data['group']['id']}/${widget.data['service_selections']['code']}');
return;
}
Map<String, dynamic> newData = widget.data;
newData['selected_plan'] = selectedPlan.value;
HttpUtil.httpPost('v1/create-service-buy-renewal-invoice',
(response) {
Routes.router.navigateTo(context, '/paynow/${response.data['order_id']}', replace: true);
},
body: newData,
).onError((error, stackTrace) {
Utils.showMessageDialog(context, error);
});
}
@override
void initState() {
super.initState();
List o = (widget.data['service_selections']['options'] as List);
for (int i = 0; i < o.length; i++) {
Map<String, dynamic> o1 = o[i];
plans.add(new KeyValue(o1['name'], o1));
}
}
}

View File

@@ -0,0 +1,357 @@
import 'package:countdown/countdown.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileChangeMobileOrEmail extends StatefulWidget {
final bool isMobile;
const MobileChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileChangeMobileOrEmailState();
}
}
class MobileChangeMobileOrEmailState extends State<MobileChangeMobileOrEmail> {
final GlobalKey<FormState> _formKey = GlobalKey();
final usernameController = TextEditingController();
bool usernameEnable = true;
final codeController = TextEditingController();
bool enableGetCode;
String getCodeText;
bool canRegister;
var countDownListener;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return ListView(
children: <Widget>[
Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
widget.isMobile ? S.of(context).change_mobile : S.of(context).change_email,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
margin: EdgeInsets.only(top: 8.0),
child: Text(
widget.isMobile ? S.of(context).change_mobile_desc : S.of(context).change_email_desc,
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
),
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: usernameController,
enabled: usernameEnable,
keyboardType: widget.isMobile ? TextInputType.phone : TextInputType.emailAddress,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: widget.isMobile ? S.of(context).mobile_number : S.of(context).email,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
if (widget.isMobile) {
return S
.of(context)
.mobile_is_required;
} else {
return S
.of(context)
.email_is_required;
}
}
if (widget.isMobile && value.trim() == store.state.user.mobile) {
return S.of(context).the_mobile_number_is_same_as_current;
}
if (!widget.isMobile && value.trim() == store.state.user.email) {
return S.of(context).the_email_is_same_as_current;
}
return null;
},
onChanged: (string) {
if (string.isEmpty) {
if (mounted) {
setState(() {
canRegister = false;
});
}
} else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canRegister = true;
});
}
}
if (string.isNotEmpty && !enableGetCode) {
if (mounted) {
setState(() {
enableGetCode = true;
});
}
} else if (string.isEmpty && enableGetCode) {
if (mounted) {
setState(() {
enableGetCode = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: codeController,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).verification_code,
suffixIcon: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Container(
margin: EdgeInsets.only(top: 6.0),
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: new Border.all(
color: enableGetCode ? Colors.black87 : Colors.black26,
width: 1.0,
),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Text(
getCodeText,
style: TextStyle(
color: enableGetCode ? Colors.black87 : Colors.black26,
fontSize: 14.0
),
),
),
),
onTap: enableGetCode ? getCodeTapped : null,
),
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).verification_code_is_required;
}
return null;
},
onChanged: (string) {
if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) {
if (mounted) {
setState(() {
canRegister = true;
});
}
} else {
if (mounted) {
setState(() {
canRegister = false;
});
}
}
},
),
),
],
),
),
),
Container(
margin: EdgeInsets.only(top: 0.0, right: 16.0),
child: Align(
alignment: Alignment.centerRight,
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).submit_to_change,
style: TextStyle(
color: Colors.white,
),
),
onPressed: canRegister ? register : null,
),
),
),
],
);
}
@override
void initState() {
super.initState();
setState(() {
enableGetCode = false;
canRegister = false;
usernameEnable = true;
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
getCodeText = S.of(context).get_code;
}
void register() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/users', (response) {
Utils.showMessageDialog(context, Exception(S.of(context).update_success), title: S.of(context).success, onOk: () {
Navigator.of(context).pop();
Routes.router.navigateTo(context, '/me', replace: true, clearStack: true);
});
},
queryParameters: {
'action': 'change-mobile-email',
},
isFormData: true,
body: {
'id': store.state.user.id,
'mobile': usernameController.text.trim(),
'code': codeController.text.trim(),
},
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}
void getCodeTapped() {
if (usernameController.text.isNotEmpty &&
((widget.isMobile && usernameController.text.trim() != store.state.user.mobile) ||
(!widget.isMobile && usernameController.text.trim() != store.state.user.email))) {
HttpUtil.httpPost('v1/users', (response) {
Fluttertoast.showToast(
msg: S.of(context).verification_code_sent,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black54,
textColor: Colors.white
);
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
startCountDown();
if (mounted) {
setState(() {
usernameEnable = false;
});
}
},
queryParameters: {
'action': 'change_mobile_email_send_code'
},
body: {
'id': store.state.user.id,
'mobile': usernameController.text,
},
isFormData: true,
).catchError((error) {
if (mounted) {
setState(() {
canRegister = false;
});
}
Utils.showMessageDialog(context, error);
});
} else {
String errorMsg = '';
if (widget.isMobile && usernameController.text.trim().isEmpty) {
errorMsg = S.of(context).mobile_is_required;
} else if (!widget.isMobile && usernameController.text.trim().isEmpty) {
errorMsg = S.of(context).email_is_required;
} else if (widget.isMobile && usernameController.text.trim() == store.state.user.mobile) {
errorMsg = S.of(context).the_mobile_number_is_same_as_current;
} else if (!widget.isMobile && usernameController.text.trim() == store.state.user.email) {
errorMsg = S.of(context).the_email_is_same_as_current;
}
Fluttertoast.showToast(
msg: errorMsg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.red,
textColor: Colors.white
);
}
}
void startCountDown() {
countDownListener.onData((Duration d) {
if (mounted) {
setState(() {
enableGetCode = false;
getCodeText = S.of(context).get_code_token(d.inSeconds);
});
}
});
countDownListener.onDone(() {
if (mounted) {
setState(() {
enableGetCode = true;
getCodeText = S.of(context).get_code_again;
});
}
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
});
}
}

View File

@@ -0,0 +1,286 @@
import 'package:flutter/material.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../generated/l10n.dart';
class MobileChangePassword extends StatefulWidget {
const MobileChangePassword({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileChangePasswordState();
}
}
class MobileChangePasswordState extends State<MobileChangePassword> {
final GlobalKey<FormState> _formKey = GlobalKey();
final oldPasswordController = TextEditingController();
final passwordController = TextEditingController();
final passwordAgainController = TextEditingController();
bool passwordVisible;
bool passwordAgainVisible;
bool canReset;
@override
void initState() {
super.initState();
canReset = false;
passwordVisible = true;
passwordAgainVisible = true;
}
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
S.of(context).change_password,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: TextFormField(
controller: oldPasswordController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).old_password,
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).current_password_is_required;
}
return null;
},
obscureText: passwordVisible,
onChanged: (string) {
if (string.trim().isNotEmpty && passwordController.text.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: passwordController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password,
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
return null;
},
obscureText: passwordVisible,
onChanged: (string) {
if (string.trim().isNotEmpty && oldPasswordController.text.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: passwordAgainController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password_again,
suffixIcon: IconButton(
icon: Icon(
passwordAgainVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordAgainVisible = !passwordAgainVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
if (value.trim() != passwordController.text.trim()) {
return S.of(context).password_is_not_match_password_again;
}
return null;
},
obscureText: passwordAgainVisible,
onChanged: (string) {
if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty && oldPasswordController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
],
),
),
),
Container(
padding: EdgeInsets.only(right: 16.0, top: 16.0, bottom: 20.0),
child: Align(
alignment: Alignment.centerRight,
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).submit,
style: TextStyle(
color: Colors.white,
),
),
onPressed: canReset ? resetPassword : null,
),
),
),
],
);
}
void resetPassword() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/users', (response) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).success),
content: Text(S.of(context).password_has_been_changed),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),
],
);
},
);
},
queryParameters: {
'action': 'change_password',
},
isFormData: true,
body: {
'id': store.state.user.id,
'old_password': oldPasswordController.text.trim(),
'password': passwordController.text.trim(),
}
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,333 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:universal_io/io.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../widgets/general/text_link.dart';
class MobileContactUs extends StatefulWidget {
final Business business;
const MobileContactUs(this.business, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileContactUsState();
}
}
class MobileContactUsState extends State<MobileContactUs> {
String mapUrl = 'https://goo.gl/maps/M365MF5AW35n9ij67';
Completer<GoogleMapController> _controller = Completer();
LatLng _lastMapPosition;
final Set<Marker> _markers = {};
final Set<Polyline> _polyLine = {};
void _onMapCreated(GoogleMapController controller) {
_controller.complete(controller);
}
void _onCameraMove(CameraPosition position) {
_lastMapPosition = position.target;
}
@override
Widget build(BuildContext context) {
Column col = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 20.0, left: 16, right: 16, bottom: 20),
child: Center(
child: Text(
S.of(context).contact_us,
style: TextStyle(
fontSize: 36,
),
),
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0, bottom: 20),
child: Row(
children: [
Container(
padding: EdgeInsets.only(right: 20),
child: Util.showImage(
'${widget.business.picUrl}',
width: 100,
height: 100,
),
),
Container(
child: Text(
'${widget.business.name}',
style: TextStyle(
fontSize: 20,
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: Colors.black38,
),
),
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8),
child: Container(
padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8),
child: Row(
children: [
Container(
padding: EdgeInsets.only(right: 8.0),
child: Icon(Icons.mail_outline, size: 18, color: Colors.black38,),
),
Text(
S.of(context).by_email,
style: TextStyle(
color: Colors.black38,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: Colors.black12,
),
),
),
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: TextLink(
'support@wisetronic.com',
'support@wisetronic.com',
isEmail: true,
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8),
child: Container(
padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8),
child: Row(
children: [
Container(
padding: EdgeInsets.only(right: 8.0),
child: Icon(Icons.phone, size: 18, color: Colors.black38,),
),
Text(
S.of(context).by_phone,
style: TextStyle(
color: Colors.black38,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: Colors.black12,
),
),
),
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: TextLink(
'905-604-8861',
'905-604-8861',
isPhone: true,
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: TextLink(
'905-604-6681',
'905-604-6681',
isPhone: true,
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: Row(
children: [
Container(
child: Text(
S.of(context).toll_free,
),
),
Container(
child: TextLink(
'1-855-278-8026',
'1-855-278-8026',
isPhone: true,
),
),
],
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 8.0, right: 8.0, top: 8, bottom: 8),
child: Container(
padding: EdgeInsets.only(left: 8, right: 8, top: 8, bottom: 8),
child: Row(
children: [
Container(
padding: EdgeInsets.only(right: 8.0),
child: Icon(Icons.location_on, size: 18, color: Colors.black38,),
),
Text(
S.of(context).address,
style: TextStyle(
color: Colors.black38,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: Colors.black12,
),
),
),
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: Text(
'${widget.business.address.addressLine1}',
),
)
);
if (widget.business.address.addressLine2 != null && widget.business.address.addressLine2.isNotEmpty) {
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: Text(
'${widget.business.address.addressLine2}',
),
)
);
}
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: Text(
'${widget.business.address.city}, ${widget.business.address.state}',
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4),
child: Text(
'${widget.business.address.country}, ${widget.business.address.zip}',
),
)
);
if (Platform.isAndroid || Platform.isIOS) {
_markers.clear();
_markers.add(Marker(
markerId: MarkerId('shop_position'),
position: LatLng(double.parse(widget.business.address.lat),
double.parse(widget.business.address.lng)),
infoWindow: InfoWindow(
title: S
.of(context)
.store,
snippet: '',
),
));
col.children.add(
Container(
height: 200,
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20),
child: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: LatLng(
double.parse(widget.business.address.lat),
double.parse(widget.business.address.lng)),
zoom: 14.0,
),
onCameraMove: _onCameraMove,
markers: _markers,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
new Factory<OneSequenceGestureRecognizer>(() => new EagerGestureRecognizer(),)
].toSet(),
),
),
);
} else {
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 20),
child: TextLink(
S.of(context).view_on_google_map,
mapUrl,
isLink: true,
),
)
);
}
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).contact_us),
backgroundColor: Theme.of(context).primaryColor,
),
body: SingleChildScrollView(
child: col,
),
);
}
}

View File

@@ -0,0 +1,307 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../generated/l10n.dart';
import '../../models/coupon.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
class MobileCoupons extends StatefulWidget {
final int contactId;
final Key key;
const MobileCoupons(this.contactId, {this.key});
@override
State<StatefulWidget> createState() {
return MobileCouponsState();
}
}
class MobileCouponsState extends State<MobileCoupons> {
List<Coupon> coupons;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (coupons == null ) {
return new Scaffold(
body: Center(
child: SpinKitWave(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
}
),
title: Text(
S.of(context).coupons,
),
),
body: ListView.builder(
itemCount: coupons.length > 0 ? coupons.length : 1,
itemBuilder: (BuildContext context, int position) {
if (coupons.length > 0) {
Coupon coupon = coupons[position];
return Container(
color: Colors.black12,
child: couponWidget(coupon),
);
} else {
return Center(
child: Container(
padding: EdgeInsets.all(20.0),
child: Text(
S.of(context).no_coupon_available,
),
),
);
}
}
),
);
}
Widget couponWidget(Coupon coupon) {
Column column = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[],
);
Row row = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: coupon.store != null ?
Util.showImage('${coupon.store.picUrl}',
fit: BoxFit.fill,
width: 40.0,
) :
Image.asset(
'assets/images/ic_launcher.png',
width: 40.0,
height: 40.0,
fit: BoxFit.fill,
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
coupon.store != null ? coupon.store.name : S.of(context).general_coupon,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
coupon.description,
style: TextStyle(
fontSize: 12.0,
color: Colors.black26,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
)
],
),
),
),
],
);
Column valueColumn = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: !coupon.isPercentage ?
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'\$',
style: TextStyle(
fontSize: 15.0,
color: Colors.redAccent,
),
),
),
Container(
child: Text(
'${Utils.smartRound(coupon.valueAmount, 2)}',
style: TextStyle(
fontSize: 28.0,
color: Colors.redAccent,
),
),
),
],
) :
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'${Utils.smartRound(coupon.valueAmount, 2)}',
style: TextStyle(
fontSize: 28.0,
color: Colors.redAccent,
),
),
),
Container(
child: Text(
S.of(context).percent_discount,
style: TextStyle(
fontSize: 15.0,
color: Colors.redAccent,
),
),
),
],
),
),
Container(
child: Text(
coupon.minAmount > 0 ?
S.of(context).available_for_order_over_token(coupon.minAmount) :
S.of(context).no_restriction,
style: TextStyle(
fontSize: 10.0,
color: Colors.black26,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
);
row.children.add(valueColumn);
column.children.add(row);
column.children.add(Container(
width: double.infinity,
margin: EdgeInsets.only(top: 10.0, bottom: 0.0),
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 0.0),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 0.5,
color: Colors.black12
)
)
),
child: SizedBox.shrink(),
));
column.children.add(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
coupon.expirationDate == null || coupon.expirationDate.length == 0 ?
S.of(context).no_expiration :
S.of(context).expiration_date_token(coupon.expirationDate),
style: TextStyle(
color: Colors.black26,
fontSize: 15.0,
),
),
),
Container(
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).redeem_coupon,
style: TextStyle(
color: Colors.white,
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
onPressed: () {
if (coupon.store != null) {
Routes.router.navigateTo(context, '/shop/${coupon.store.id}/na/na/na');
} else {
Routes.router.navigateTo(context, '/businesses');
}
},
),
),
],
),
);
return Container(
margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0, bottom: 5.0),
padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 10.0, right: 10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
bottomRight: Radius.circular(10.0),
),
),
child: column,
);
}
@override
void initState() {
super.initState();
coupons = null;
HttpUtil.httpGet(
'v1/coupons'
).then((data) {
if (mounted) {
setState(() {
coupons =
(data as List).map((e) => Coupon.fromJson(e)).toList();
});
}
}).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}

View File

@@ -35,7 +35,9 @@ class MobileDownloadAppsState extends State<MobileDownloadApps> {
for (int i = 0; i < apps.length; i++) {
col.children.add(apps[i]);
}
return col;
return SingleChildScrollView(
child: col,
);
}
List<Widget> _getApps() {

View File

@@ -0,0 +1,565 @@
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_wisetronic/constants.dart';
import 'package:flutter_wisetronic/events/eventbus.dart';
import 'package:flutter_wisetronic/events/events.dart';
import 'package:flutter_wisetronic/generated/l10n.dart';
import 'package:flutter_wisetronic/models/address.dart';
import 'package:flutter_wisetronic/store/actions.dart';
import 'package:flutter_wisetronic/store/store.dart';
import 'package:flutter_wisetronic/utils/http_util.dart';
import 'package:flutter_wisetronic/utils/utils.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:gender_selection/gender_selection.dart';
import '../../routes.dart';
class MobileEditAddress extends StatefulWidget {
final Key key;
final Address address;
final int businessId;
const MobileEditAddress(this.address, {this.key, int businessId}) :
businessId = businessId ?? Constants.BUSINESS_ID;
@override
State<StatefulWidget> createState() {
return MobileEditAddressState();
}
}
class MobileEditAddressState extends State<MobileEditAddress> {
final GlobalKey<FormState> _formKey = new GlobalKey();
final contactNameController = TextEditingController();
final phoneController = TextEditingController();
final streetLine1Controller = TextEditingController();
final streetLine2Controller = TextEditingController();
final cityController = TextEditingController();
final postalCodeController = TextEditingController();
final emailController = TextEditingController();
final faxController = TextEditingController();
String country;
Gender _selectedGender;
String _selectedProvince;
bool showLoading;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (showLoading) {
return new Scaffold(
body: Center(
child: SpinKitWave(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(S
.of(context)
.edit_address),
backgroundColor: Theme
.of(context)
.primaryColor,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.only(
left: 0.0, right: 0.0, top: 0.0, bottom: 0.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: contactNameController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.contact_name,
),
validator: (String value) {
if (value
.trim()
.isEmpty) {
return S
.of(context)
.contact_name_is_required;
}
return null;
},
),
),
GenderSelection(
maleText: S
.of(context)
.mr,
femaleText: S
.of(context)
.ms,
selectedGenderIconBackgroundColor: Colors.indigo,
checkIconAlignment: Alignment.bottomRight,
selectedGenderCheckIcon: Icons.check,
onChanged: (Gender gender) {
_selectedGender = gender;
},
equallyAligned: true,
animationDuration: Duration(milliseconds: 400),
isCircular: true,
isSelectedGenderIconCircular: true,
opacityOfGradient: 0.6,
padding: const EdgeInsets.all(3.0),
size: 50,
selectedGender: _selectedGender,
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
keyboardType: TextInputType.phone,
controller: phoneController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.mobile_phone_number,
),
validator: (String value) {
if (value
.trim()
.isEmpty) {
return S
.of(context)
.mobile_phone_number_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: streetLine1Controller,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.street_line_1,
),
validator: (String value) {
if (value
.trim()
.isEmpty) {
return S
.of(context)
.street_line_1_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: streetLine2Controller,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.street_line_2,
),
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: cityController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.city,
),
validator: (String value) {
if (value
.trim()
.isEmpty) {
return S
.of(context)
.city_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: DropdownButton<String>(
value: _selectedProvince,
items: <String>[
'Ontario',
'Quebec',
'British Columbia',
'Alberta',
'Manitoba',
'Saskatchewan',
'Nova Scotia',
'New Brunswich',
'Newfoundland and Labrador',
'Prince Edward Island',
'Northwest Territories',
'Nunavut',
'Yukon',
].map((value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
if (mounted) {
setState(() {
_selectedProvince = newValue;
});
}
},
hint: Text(S
.of(context)
.province),
isExpanded: true,
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: postalCodeController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.postal_code,
),
validator: (String value) {
if (value
.trim()
.isEmpty) {
return S
.of(context)
.postal_code_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 32.0, bottom: 0.0),
child: Text(
S
.of(context)
.optional_information,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 0.0, bottom: 0.0),
child: TextFormField(
controller: emailController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.email,
),
validator: (String value) {
if (value.isNotEmpty && !EmailValidator.validate(value)) {
return S
.of(context)
.email_is_not_valid;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: TextFormField(
controller: faxController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S
.of(context)
.fax,
),
),
),
],
),
),
),
bottomNavigationBar: Container(
padding: EdgeInsets.all(8.0),
color: Theme
.of(context)
.buttonColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FlatButton(
child: Text(
S
.of(context)
.delete,
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S
.of(context)
.warning),
content: Text(S
.of(context)
.are_you_sure_to_delete_the_address),
actions: <Widget>[
FlatButton(
child: Text(S
.of(context)
.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text(S
.of(context)
.yes_i_am_sure),
onPressed: () {
Navigator.of(context).pop();
_deleteAddress();
},
)
],
);
}
);
},
),
FlatButton(
child: Text(
S
.of(context)
.save
),
onPressed: () {
_saveEditAddress();
},
),
],
),
),
);
}
@override
void initState() {
super.initState();
setState(() {
showLoading = false;
_selectedProvince = widget.address.state;
cityController.text = widget.address.city;
postalCodeController.text = widget.address.zip;
streetLine1Controller.text = widget.address.addressLine1;
streetLine2Controller.text = widget.address.addressLine2;
_selectedGender = widget.address.gender == 1 ? Gender.Male : Gender.Female;
country = widget.address.country;
contactNameController.text = widget.address.contactName;
phoneController.text = widget.address.phone;
emailController.text = widget.address.email;
faxController.text = widget.address.fax;
});
}
void _saveEditAddress() {
final FormState form = _formKey.currentState;
if (form.validate()) {
if (mounted) {
setState(() {
showLoading = true;
});
}
HttpUtil.httpPatch('v1/addresses/${widget.address.id}', (response) {
if (mounted) {
setState(() {
showLoading = false;
});
}
eventBus.fire(OnAddressesUpdated());
if (widget.businessId > 0) {
Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true);
} else {
Navigator.of(context).pop();
}
},
body: {
'id': widget.address.id,
'name': contactNameController.text.trim(),
'address_line1': streetLine1Controller.text.trim(),
'address_line2': streetLine2Controller.text.trim(),
'city': cityController.text.trim(),
'state': _selectedProvince,
'zip': postalCodeController.text.trim(),
'phone': phoneController.text.trim(),
'gender': _selectedGender == Gender.Male ? true : false,
'email': emailController.text,
'fax': faxController.text,
'country': country,
}
).catchError((error) {
if (mounted) {
setState(() {
showLoading = false;
});
}
Utils.showMessageDialog(context, error);
});
}
}
void _deleteAddress() {
if (mounted) {
setState(() {
showLoading = true;
});
}
HttpUtil.httpDelete('v1/addresses/${widget.address.id}', (response) {
if (mounted) {
setState(() {
showLoading = false;
});
}
Fluttertoast.showToast(
msg: S.of(context).the_address_has_been_deleted,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black54,
textColor: Colors.white
);
eventBus.fire(OnAddressesUpdated());
Navigator.of(context).pop();
}).catchError((error) {
if (mounted) {
setState(() {
showLoading = false;
});
}
Utils.showMessageDialog(context, error);
});
}
}

View File

@@ -0,0 +1,321 @@
import 'package:countdown/countdown.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileForgotPassword extends StatefulWidget {
const MobileForgotPassword({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileForgotPasswordState();
}
}
class MobileForgotPasswordState extends State<MobileForgotPassword> {
final GlobalKey<FormState> _formKey = GlobalKey();
final usernameController = TextEditingController();
bool usernameEnable = true;
final codeController = TextEditingController();
bool enableGetCode;
String getCodeText;
bool canRegister;
var countDownListener;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return ListView(
children: <Widget>[
Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
S.of(context).forgot_password,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
margin: EdgeInsets.only(top: 8.0),
child: Text(
S.of(context).forgot_password_description,
style: TextStyle(
fontSize: 14.0,
color: Colors.black38
),
),
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: usernameController,
enabled: usernameEnable,
keyboardType: TextInputType.emailAddress,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).mobile_or_email,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).mobile_or_email_is_required;
}
return null;
},
onChanged: (string) {
if (string.isEmpty) {
if (mounted) {
setState(() {
canRegister = false;
});
}
} else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canRegister = true;
});
}
}
if (string.isNotEmpty && !enableGetCode) {
if (mounted) {
setState(() {
enableGetCode = true;
});
}
} else if (string.isEmpty && enableGetCode) {
if (mounted) {
setState(() {
enableGetCode = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: codeController,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).verification_code,
suffixIcon: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Container(
margin: EdgeInsets.only(top: 6.0),
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: new Border.all(
color: enableGetCode ? Colors.black87 : Colors.black26,
width: 1.0,
),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Text(
getCodeText,
style: TextStyle(
color: enableGetCode ? Colors.black87 : Colors.black26,
fontSize: 12.0
),
),
),
),
onTap: enableGetCode ? getCodeTapped : null,
),
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).verification_code_is_required;
}
return null;
},
onChanged: (string) {
if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) {
if (mounted) {
setState(() {
canRegister = true;
});
}
} else {
if (mounted) {
setState(() {
canRegister = false;
});
}
}
},
),
),
],
),
),
),
Container(
margin: EdgeInsets.only(top: 0.0, right: 16.0),
child: Align(
alignment: Alignment.centerRight,
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).verify,
style: TextStyle(
color: Colors.white,
),
),
onPressed: canRegister ? register : null,
),
),
),
],
);
}
@override
void initState() {
super.initState();
setState(() {
enableGetCode = false;
canRegister = false;
usernameEnable = true;
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
getCodeText = S.of(context).get_code;
}
void register() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/users', (response) {
Routes.router.navigateTo(context, '/reset-password/${usernameController.text.trim()}/${codeController.text.trim()}', replace: true);
},
queryParameters: {
'action': 'forgot_password_verify_code'
},
isFormData: true,
body: {
'mobile': usernameController.text.trim(),
'code': codeController.text.trim(),
},
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}
void getCodeTapped() {
if (usernameController.text.trim().isNotEmpty) {
HttpUtil.httpPost('v1/users', (response) {
Fluttertoast.showToast(
msg: S.of(context).verification_code_sent,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black54,
textColor: Colors.white
);
if (mounted) {
setState(() {
usernameEnable = false;
});
}
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
startCountDown();
},
queryParameters: {'action': 'forgot_password'},
body: {
'mobile': usernameController.text.trim(),
},
isFormData: true,
).catchError((error) {
if (mounted) {
setState(() {
canRegister = false;
});
}
Utils.showMessageDialog(context, error);
});
} else {
Fluttertoast.showToast(
msg: S.of(context).enter_mobile_or_email,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.red,
textColor: Colors.white
);
}
}
void startCountDown() {
countDownListener.onData((Duration d) {
if (mounted) {
setState(() {
enableGetCode = false;
getCodeText = S.of(context).get_code_token(d.inSeconds);
});
}
});
countDownListener.onDone(() {
if (mounted) {
setState(() {
enableGetCode = true;
getCodeText = S.of(context).get_code_again;
});
}
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
});
}
}

View File

@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
class MobileiGoShowLearnMore extends StatefulWidget {
final Map<String, dynamic> data;
const MobileiGoShowLearnMore(this.data, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileiGoShowLearnMoreState();
}
}
class MobileiGoShowLearnMoreState extends State<MobileiGoShowLearnMore> {
@override
Widget build(BuildContext context) {
Column col = Column(
children: [
Container(
margin: EdgeInsets.only(bottom: 20.0),
child: Util.showImage(
'https:${widget.data['image-top']['image']}',
fit: BoxFit.contain,
),
),
],
);
List<Widget> w = _getContent();
for (int i = 0; i < w.length; i++) {
col.children.add(w[i]);
}
return SingleChildScrollView(
child: col,
);
}
@override
void initState() {
super.initState();
}
List<Widget> _getContent() {
List<Widget> widgets = [];
for (int i = 0; i < (widget.data['sections'] as List).length; i++) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
col.children.add(Container(
margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0),
child: Text(
'${(widget.data['sections'] as List)[i]['title']}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: Colors.black87,
),
),
));
if ((widget.data['sections'] as List)[i]['image'] != null) {
col.children.add(
Container(
padding: EdgeInsets.only(
top: 10.0, left: 8.0, right: 8.0, bottom: 8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Util.showImage(
'https:${(widget
.data['sections'] as List)[i]['image']['image']}',
fit: BoxFit.contain,
),
),
),
);
} else if (((widget.data['sections'] as List)[i]['youtube_videos'] as List).length > 0) {
YoutubePlayerController _controller = YoutubePlayerController(
initialVideoId: '${((widget.data['sections'] as List)[i]['youtube_videos'] as List)[0] as String}',
params: YoutubePlayerParams(
playlist: ((widget.data['sections'] as List)[i]['youtube_videos'] as List).length > 1 ?
((widget.data['sections'] as List)[i]['youtube_videos'] as List).map((e) {
return '$e';
}).toList() : [], // Defining custom playlist
startAt: Duration(seconds: 0),
showControls: true,
showFullscreenButton: false,
),
);
col.children.add(
Container(
padding: EdgeInsets.only(
top: 10.0, left: 8.0, right: 8.0, bottom: 8.0,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: YoutubePlayerIFrame(
controller: _controller,
aspectRatio: 2 / 1,
),
),
),
);
}
col.children.add(Container(
margin: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 20.0),
padding: EdgeInsets.only(bottom: 10.0),
child: Text(
'${(widget.data['sections'] as List)[i]['description']}',
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
),
));
widgets.add(col);
}
return widgets;
}
}

View File

@@ -31,7 +31,7 @@ class MobileIndexMainContent1State extends State<MobileIndexMainContent1> {
),
),
decoration: BoxDecoration(
color: Colors.lightGreen,
color: Color(0xFF4FB0C6),
borderRadius: BorderRadius.circular(10),
),
);

View File

@@ -58,7 +58,7 @@ class MobileIndexMainContent2State extends State<MobileIndexMainContent2> {
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Util.showImage(
'http:${widget.content['minipos_image']['image']}',
'https:${widget.content['minipos_image']['image']}',
fit: BoxFit.fitWidth
),
),
@@ -103,6 +103,7 @@ class MobileIndexMainContent2State extends State<MobileIndexMainContent2> {
child: TextLink(
S.of(context).learn_more,
'/minipos-learn-more',
fontWeight: FontWeight.bold,
),
),
);

View File

@@ -58,7 +58,7 @@ class MobileIndexMainContent3State extends State<MobileIndexMainContent3> {
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Util.showImage(
'http:${widget.content['igoshow_image']['image']}',
'https:${widget.content['igoshow_image']['image']}',
fit: BoxFit.fitWidth
),
),
@@ -106,6 +106,7 @@ class MobileIndexMainContent3State extends State<MobileIndexMainContent3> {
child: TextLink(
S.of(context).learn_more,
'/igoshow-learn-more',
fontWeight: FontWeight.bold,
),
),
);

View File

@@ -0,0 +1,275 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../models/user.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../constants.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
class MobileLogin extends StatefulWidget {
final Key key;
const MobileLogin({this.key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileLoginState();
}
}
class MobileLoginState extends State<MobileLogin> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final usernameController = TextEditingController();
final passwordController = TextEditingController();
bool passwordVisible;
bool onSubmitting;
@override
void initState() {
super.initState();
passwordVisible = true;
onSubmitting = false;
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (onSubmitting) {
return Container(
padding: EdgeInsets.all(50.0),
child: Center(
child: SpinKitThreeBounce(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
Widget view = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0),
child: Text(
S.of(context).please_login,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0),
child: Text(
S.of(context).login_instruction,
style: TextStyle(
color: Colors.black54,
fontSize: 14.0,
),
),
),
Form(
key: _formKey,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 0.0),
child: TextFormField(
controller: usernameController,
keyboardType: TextInputType.emailAddress,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).mobile_email_username,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).this_field_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 5.0),
child: TextFormField(
controller: passwordController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password,
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
obscureText: passwordVisible,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
return null;
},
),
),
],
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
FlatButton(
child: Text(
S.of(context).new_user_question,
style: TextStyle(
fontSize: 14.0,
),
),
onPressed: () {
newUser();
},
),
Expanded(
child: Text(''),
),
Container(
child: FlatButton(
child: Text(
S.of(context).forgot_password_question,
style: TextStyle(
fontSize: 14.0,
),
),
onPressed: () {
forgotPassword();
},
),
),
],
),
),
Align(
alignment: Alignment.centerRight,
child: Container(
padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0),
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).login,
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {
signIn();
},
),
),
),
],
);
return SingleChildScrollView(
child: view,
);
}
void newUser() {
Routes.router.navigateTo(context, '/new-user');
}
void forgotPassword() {
Routes.router.navigateTo(context, '/forgot-password');
}
void signIn() {
final FormState form = _formKey.currentState;
if (form.validate()) {
if (mounted) {
setState(() {
onSubmitting = true;
});
}
HttpUtil.httpPost(
'v1/oauth-wisetronic/access_token', (response) {
print('response $response');
User user = User.fromJson(response.data['user']);
store.dispatch(UpdateCurrentUser(user));
Utils.getBox().then((box) {
box.put(Constants.KEY_USER_ID, response.data['user_id']);
box.put(Constants.KEY_ACCESS_TOKEN, response.data['access_token']);
if (store.state.redirectRoute != null) {
Routes.router.navigateTo(context, store.state.redirectRoute, replace: true);
store.dispatch(UpdateRedirectRoute(null));
} else {
Routes.router.navigateTo(
context, '/me', replace: true, clearStack: false);
}
}).catchError((error) {
Utils.showMessageDialog(context, error);
});
},
queryParameters: {
'client_id': '${Utils.getPlatformName()}',
'grant_type': 'password'
},
body: {
'username': usernameController.text.trim(),
'password': passwordController.text.trim(),
'fcm_token': store.state.fcmToken != null ? store.state.fcmToken : ''
},
isFormData: true,
businessId: Constants.BUSINESS_ID,
).catchError((error) {
if (mounted) {
setState(() {
onSubmitting = false;
});
}
Utils.showMessageDialog(context, error);
});
}
}
}

View File

@@ -0,0 +1,842 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../constants.dart';
import '../../dialog/logout_dialog.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/user.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/shop_scroll_controller.dart';
import '../../utils/shop_scroll_coordinator.dart';
import '../../utils/util_web.dart'
if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
MediaQueryData mediaQuery;
double statusBarHeight;
double screenHeight;
class MobileMe extends StatefulWidget {
final Key key;
const MobileMe({this.key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileMeState();
}
}
class MobileMeState extends State<MobileMe> {
int userId;
String accessToken;
bool isLoading;
User _user;
ShopScrollCoordinator _shopCoordinator;
ShopScrollController _pageScrollController;
final double _sliverAppBarInitHeight = 165.0;
final double _appBarHeight = 85.0;
ShopScrollController _listScrollController1;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (isLoading) {
return new Scaffold(
body: Center(
child: SpinKitWave(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
mediaQuery ??= MediaQuery.of(context);
screenHeight ??= mediaQuery.size.height;
statusBarHeight ??= mediaQuery.padding.top;
_pageScrollController ??=
_shopCoordinator.pageScrollController(_sliverAppBarInitHeight * -1.0);
_shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () {
return statusBarHeight + kToolbarHeight + _appBarHeight;
};
_listScrollController1 = _shopCoordinator.newChildScrollController();
Widget userInfo = GestureDetector(
child: Container(
padding:
EdgeInsets.only(left: 16.0, right: 16.0, top: 5.0, bottom: 5.0),
margin: EdgeInsets.only(top: kToolbarHeight + 40.0),
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 5.0),
child: _user != null && _user.avatarUrl.isNotEmpty
? Util.showImage(
'https:${_user.avatarUrl}',
width: 60,
height: 60,
fit: BoxFit.fill,
errorWidget: (context, url, error) => Icon(
Icons.account_circle,
size: 60.0,
color: Theme.of(context).scaffoldBackgroundColor,
),
)
: Icon(
Icons.person_outline,
size: 60.0,
color: Colors.white38,
),
),
Expanded(
child: Container(
height: 60.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
_user != null
? _user.nickname
: S.of(context).please_login,
style: TextStyle(
color: Theme.of(context).scaffoldBackgroundColor,
fontSize: 28.0,
),
),
),
Visibility(
visible: _user != null,
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Icon(
Icons.phone_iphone,
color:
Theme.of(context).scaffoldBackgroundColor,
size: 20.0,
),
),
Container(
child: Text(
_user != null
? Utils.safePhoneNumber(_user.mobile)
: '',
style: TextStyle(
color:
Theme.of(context).scaffoldBackgroundColor,
fontSize: 16.0,
),
),
)
],
),
),
),
],
),
),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
color: Theme.of(context).scaffoldBackgroundColor,
size: 20.0,
),
color: Colors.transparent,
),
],
),
),
onTap: () {
if (_user != null) {
Routes.router.navigateTo(context, '/user-profile');
} else {
Routes.router.navigateTo(context, '/login');
}
},
);
Listener listener = Listener(
onPointerUp: _shopCoordinator.onPointerUp,
child: CustomScrollView(
controller: _pageScrollController,
physics: ClampingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
pinned: true,
floating: true,
snap: true,
title: Text(S.of(context).me),
centerTitle: true,
backgroundColor: Theme.of(context).primaryColor,
expandedHeight: _sliverAppBarInitHeight,
actions: <Widget>[],
flexibleSpace: FlexibleSpaceBar(
background: userInfo,
collapseMode: CollapseMode.none,
),
),
SliverPersistentHeader(
pinned: true,
floating: false,
delegate: _SliverAppBarDelegate(
minHeight: _appBarHeight,
maxHeight: _appBarHeight,
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
_user != null
? '${_user.wallet.toStringAsFixed(2)}'
: '0.00',
style: TextStyle(
fontSize: 24.0,
color: Colors.redAccent,
),
),
),
Container(
child: Text(
S.of(context).wallet,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
)
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 6.0,
),
right: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
),
Expanded(
child: GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
_user != null ? '${_user.coupon}' : '0',
style: TextStyle(
fontSize: 24.0,
color: Colors.orangeAccent,
),
),
),
Container(
child: Text(
S.of(context).red_coupon,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
)
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 6.0,
),
right: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
onTap: () {
if (_user != null) {
Routes.router
.navigateTo(context, '/coupons/${_user.id}');
} else {
_pleaseLoginToast();
}
},
),
),
Expanded(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
_user != null ? '${_user.points}' : '0',
style: TextStyle(
fontSize: 24.0,
color: Colors.lightGreen,
),
),
),
Container(
child: Text(
S.of(context).point,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
)
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 6.0,
),
),
),
),
),
],
),
),
),
),
SliverFillRemaining(
child: ListView.builder(
controller: _listScrollController1,
itemCount: 7,
itemBuilder: (BuildContext context, int i) {
Widget item;
switch (i) {
case 0:
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.settings_overscan,
size: 20.0,
),
),
Container(
child: Text(
S.of(context).ocr_scan,
style: TextStyle(
fontSize: 15.0,
),
),
),
Expanded(
child: SizedBox(),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 15.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
if (_user != null) {
if (kIsWeb) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).error),
content: Text(
S.of(context).feature_not_available_web,
),
actions: [
FlatButton(
onPressed: () {
Routes.router.pop(context);
},
child: Text(S.of(context).ok)),
],
);
});
} else {
Routes.router.navigateTo(
context,
'/ocr-scan',
);
}
} else {
_pleaseLoginToast();
}
},
);
break;
case 1:
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.lock,
size: 20.0,
),
),
Container(
child: Text(
S.of(context).change_password,
style: TextStyle(
fontSize: 15.0,
),
),
),
Expanded(
child: SizedBox(),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 15.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
if (_user != null) {
Routes.router.navigateTo(
context,
'/change-password',
);
} else {
_pleaseLoginToast();
}
},
);
break;
case 2:
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.sticky_note_2_outlined,
size: 20.0,
),
),
Container(
child: Text(
S.of(context).my_orders,
style: TextStyle(
fontSize: 15.0,
),
),
),
Expanded(
child: SizedBox(),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 15.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
if (_user != null) {
Routes.router.navigateTo(context, '/orders');
} else {
_pleaseLoginToast();
}
},
);
break;
case 3:
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.location_on,
size: 20.0,
),
),
Container(
child: Text(
S.of(context).my_addresses,
style: TextStyle(
fontSize: 15.0,
),
),
),
Expanded(
child: SizedBox(),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 15.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
if (_user != null) {
Routes.router.navigateTo(context, '/my-addresses/-1');
} else {
_pleaseLoginToast();
}
},
);
break;
case 4:
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.credit_card,
size: 20.0,
),
),
Container(
child: Text(
S.of(context).my_cards,
style: TextStyle(
fontSize: 15.0,
),
),
),
Expanded(
child: SizedBox(),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 15.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
if (_user != null) {
Routes.router.navigateTo(context, '/my-cards');
} else {
_pleaseLoginToast();
}
},
);
break;
case 5:
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.headset_mic,
size: 20.0,
),
),
Container(
child: Text(
S.of(context).my_support,
style: TextStyle(
fontSize: 15.0,
),
),
),
Expanded(
child: SizedBox(),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 15.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
if (_user != null) {
if (_user.email == null || _user.email.isEmpty) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).warning),
content: Text(S.of(context).email_needed),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
RaisedButton(
color: Theme.of(context).primaryColor,
textColor: Colors.white,
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).pop();
Routes.router.navigateTo(context,
'/change-mobile-email/2');
},
),
],
);
});
} else {
Routes.router.navigateTo(context,
'/my-support/${Constants.BUSINESS_ID}');
}
} else {
_pleaseLoginToast();
}
},
);
break;
case 6:
if (_user == null) {
item = SizedBox.shrink();
} else {
item = GestureDetector(
child: Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
child: Text(
S.of(context).logout,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
),
Container(
margin: EdgeInsets.only(left: 10.0),
child: Icon(
Icons.logout,
color: Colors.redAccent,
),
),
],
),
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12, width: 1.0))),
),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return logoutDialog(context);
});
},
);
}
break;
}
return item;
},
),
),
],
),
);
return listener;
}
@override
void initState() {
super.initState();
setState(() {
isLoading = true;
_user = null;
});
_shopCoordinator = ShopScrollCoordinator();
eventBus.on<OnCurrentUserUpdated>().listen((event) {
if (mounted) {
setState(() {
isLoading = false;
_user = store.state.user;
});
}
});
eventBus.on<OnGetCurrentUserFailed>().listen((event) {
if (mounted) {
setState(() {
isLoading = false;
_user = null;
});
}
});
Utils.getCurrentUser();
}
void _toLogin() {
Routes.router
.navigateTo(context, '/login', replace: true, clearStack: true);
}
_pleaseLoginToast() {
Fluttertoast.showToast(
msg: S.of(context).please_login,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black54,
textColor: Colors.white);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => this.minHeight;
@override
double get maxExtent => max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}

View File

@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
class MobileMiniPosLearnMore extends StatefulWidget {
final Map<String, dynamic> data;
const MobileMiniPosLearnMore(this.data, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileMiniPosLearnMoreState();
}
}
class MobileMiniPosLearnMoreState extends State<MobileMiniPosLearnMore> {
@override
Widget build(BuildContext context) {
Column col = Column(
children: [
Container(
margin: EdgeInsets.only(bottom: 20.0),
child: Util.showImage(
'https:${widget.data['image-top']['image']}',
fit: BoxFit.contain,
),
),
],
);
List<Widget> w = _getContent();
for (int i = 0; i < w.length; i++) {
col.children.add(w[i]);
}
return SingleChildScrollView(
child: col,
);
}
@override
void initState() {
super.initState();
}
List<Widget> _getContent() {
List<Widget> widgets = [];
for (int i = 0; i < (widget.data['sections'] as List).length; i++) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
col.children.add(Container(
margin: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0),
child: Text(
'${(widget.data['sections'] as List)[i]['title']}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: Colors.black87,
),
),
));
col.children.add(
Container(
padding: EdgeInsets.only(top: 10.0, left: 8.0, right: 8.0, bottom: 8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Util.showImage(
'https:${(widget.data['sections'] as List)[i]['image']['image']}',
fit: BoxFit.contain,
),
),
),
);
col.children.add(Container(
margin: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 20.0),
padding: EdgeInsets.only(bottom: 10.0),
child: Text(
'${(widget.data['sections'] as List)[i]['description']}',
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
),
));
widgets.add(col);
}
return widgets;
}
}

View File

@@ -0,0 +1,227 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_wisetronic/events/eventbus.dart';
import 'package:flutter_wisetronic/events/events.dart';
import 'package:flutter_wisetronic/generated/l10n.dart';
import 'package:flutter_wisetronic/models/address.dart';
import 'package:flutter_wisetronic/pages/edit_address.dart';
import 'package:flutter_wisetronic/store/actions.dart';
import 'package:flutter_wisetronic/store/store.dart';
import 'package:flutter_wisetronic/utils/http_util.dart';
import 'package:flutter_wisetronic/utils/utils.dart';
import 'package:flutter_wisetronic/widgets/mobile/MobileBottomNav.dart';
import '../../routes.dart';
class MobileMyAddresses extends StatefulWidget {
final int businessId;
const MobileMyAddresses({Key key, this.businessId}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileMyAddressesState();
}
}
class MobileMyAddressesState extends State<MobileMyAddresses> {
List<Address> addresses;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (addresses == null) {
return new Scaffold(
body: Center(
child: SpinKitWave(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
BuildContext mainContext = context;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(S.of(context).my_addresses),
backgroundColor: Theme.of(context).primaryColor,
actions: [
IconButton(
padding: EdgeInsets.only(right: 16.0),
icon: Icon(
Icons.add,
size: 32.0,
),
onPressed: () {
Routes.router.navigateTo(context, '/search-place/${widget.businessId}');
}
),
],
),
body: ListView.builder(
itemCount: addresses.length <= 1 ? 1 : addresses.length,
itemBuilder: (BuildContext context, int i) {
if (addresses.length <= 0) {
return Container(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(S.of(context).no_address_yet),
),
);
} else {
Address address = addresses[i];
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.6,
)
)
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
address.addressLine1,
style: TextStyle(
fontSize: 19.0,
),
),
),
Container(
child: Text(
address.addressLine2,
style: TextStyle(
fontSize: 13.0,
color: Colors.grey,
),
),
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: Text(
'${address.contactName} ${Utils.safePhoneNumber(address.phone)}',
style: TextStyle(
fontSize: 15.0,
color: Colors.black87,
),
),
)
],
),
),
Container(
child: widget.businessId == 0 ?
IconButton(
icon: Icon(
Icons.edit,
color: Colors.grey,
),
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => new EditAddress(address, businessId: 0,),
));
},
) :
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 5.0),
child: IconButton(
icon: Icon(
Icons.edit,
color: Colors.grey,
),
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => new EditAddress(address, businessId: widget.businessId,),
));
},
),
),
Container(
child: widget.businessId > 0 ? IconButton(
icon: Icon(
Icons.check,
color: Colors.grey,
),
onPressed: () {
HttpUtil.httpPut('v1/select-address/${address.id}', (response) {
Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true);
}).catchError((error) {
Utils.showMessageDialog(mainContext, error, onOk: () {
Navigator.of(mainContext).pop();
Navigator.of(mainContext).pop();
});
});
},
) : SizedBox.shrink(),
)
],
),
),
],
),
);
}
}
),
bottomNavigationBar: MobileBottomNav(currentIndex: 3,),
);
}
@override
void initState() {
super.initState();
loadAddresses();
eventBus.on<OnAddressesUpdated>().listen((event) {
if (mounted) {
setState(() {
addresses = null;
});
}
loadAddresses();
});
}
void loadAddresses() {
HttpUtil.httpGet(
'v1/addresses',
).then((value) {
if (mounted) {
setState(() {
addresses = (value as List).map((e) => Address.fromJson(e)).toList();
});
}
}).catchError((error) {
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
});
}
}

View File

@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/stripe_payment_method.dart';
import '../../models/user.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileMyCards extends StatefulWidget {
final Key key;
const MobileMyCards({this.key});
@override
State<StatefulWidget> createState() {
return MyCardsState();
}
}
class MyCardsState extends State<MobileMyCards> {
User _user;
bool isSubmitting;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).my_cards),
backgroundColor: Theme.of(context).primaryColor,
),
body: ListView.builder(
itemCount: _user.stripePaymentMethods.length,
itemBuilder: (BuildContext context, int position) {
StripePaymentMethod paymentMethod = _user.stripePaymentMethods[position];
return cardWidget(paymentMethod);
}
),
);
}
Widget cardWidget(StripePaymentMethod paymentMethod) {
return Container(
padding: EdgeInsets.only(
top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
paymentMethod.cardBrand == 'visa' ?
Image.asset(
'assets/images/visa.png',
width: 50.0,
height: 50.0,
fit: BoxFit.fill,
) : (paymentMethod.cardBrand == 'mastercard' ?
Image.asset(
'assets/images/master.png',
width: 50.0,
height: 50.0,
fit: BoxFit.fill,
) : Icon(
Icons.credit_card, size: 50.0, color: Colors.black38,)),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 20.0, right: 10.0),
child: Text(
'**** ${paymentMethod.cardLast4}',
style: TextStyle(
fontSize: 20.0,
),
),
),
Container(
margin: EdgeInsets.only(left: 20.0, right: 10.0),
child: Text(
S.of(context).expire_token(paymentMethod.cardExpMonth, paymentMethod.cardExpYear),
style: TextStyle(
fontSize: 14.0,
color: Colors.black26,
),
),
),
],
),
),
IconButton(
icon: Icon(Icons.clear, color: Colors.black26,),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).warning),
content: Text(
S.of(context).are_you_sure_to_remove_the_card,
),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).cancel),
color: Theme.of(context).primaryColor,
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text(S.of(context).yes_i_am_sure),
onPressed: () {
Navigator.of(context).pop();
_removeCard(paymentMethod);
},
)
],
);
}
);
},
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
);
}
_removeCard(StripePaymentMethod paymentMethod) {
HttpUtil.httpDelete('v1/stripe-card/${paymentMethod.id}', (response) {
_user = User.fromJson(response.data);
store.dispatch(UpdateCurrentUser(_user));
eventBus.fire(OnCurrentUserUpdated());
if (mounted) {
Navigator.of(context).pop();
setState(() {
isSubmitting = false;
});
}
}).catchError((error) {
if (isSubmitting) {
Navigator.of(context).pop();
isSubmitting = false;
}
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
});
isSubmitting = true;
Utils.showSubmitDialog(context);
}
@override
void initState() {
super.initState();
setState(() {
isSubmitting = false;
_user = store.state.user;
});
}
}

View File

@@ -0,0 +1,295 @@
import 'package:flutter/material.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/ticket.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../widgets/general/double_back_to_close_app_wrapper.dart';
import '../../widgets/mobile/MobileBottomNav.dart';
class MobileMySupport extends StatefulWidget {
final int businessId;
const MobileMySupport({Key key, this.businessId}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileMySupportState();
}
}
class MobileMySupportState extends State<MobileMySupport> {
List<Ticket> tickets;
int _page = 1;
int _pageCount = 1;
bool _isLoading = false;
bool _loadingFinish = false;
RefreshController _refreshController = RefreshController(initialRefresh: true);
void _onRefresh() {
_page = 1;
if (tickets != null) {
tickets.clear();
} else {
tickets = [];
}
_refreshController.resetNoData();
loadTicketes(true);
}
void _onLoadMore() {
// if failed,use loadFailed(),if no data return,use LoadNodata()
if (_pageCount > _page) {
_page += 1;
loadTicketes(false);
} else {
_refreshController.loadNoData();
}
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (store.state.user == null) {
Utils.requireLogin(context, returnUrl: '/my-support/${widget.businessId}');
return;
}
});
BuildContext mainContext = context;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(S.of(context).my_support),
backgroundColor: Theme.of(context).primaryColor,
actions: [
IconButton(
padding: EdgeInsets.only(right: 16.0),
icon: Icon(
Icons.add,
size: 32.0,
),
onPressed: () {
Routes.router.navigateTo(context, '/new-ticket/${widget.businessId}');
}
),
],
),
body: DoubleBackToCloseAppWrapper(
child: SmartRefresher(
enablePullDown: true,
enablePullUp: true,
header: WaterDropHeader(),
footer: CustomFooter(
builder: (BuildContext context, LoadStatus mode){
Widget footer;
if(mode == LoadStatus.idle) {
footer = Text(S.of(context).pull_up_to_load_more);
} else if (mode == LoadStatus.loading) {
footer = CircularProgressIndicator();
} else if (mode == LoadStatus.failed) {
footer = Text(S.of(context).load_failed_retry);
} else if (mode == LoadStatus.canLoading) {
footer = Text(S.of(context).release_to_load_more);
} else if (mode == LoadStatus.noMore) {
footer = Text(S.of(context).no_more_record);
} else {
footer = Text('...');
}
return Container(
height: 55.0,
child: Center(child: footer,),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoadMore,
child: _buildBody(),
),
),
bottomNavigationBar: MobileBottomNav(currentIndex: 2,),
);
}
@override
void dispose() {
_refreshController?.dispose();
super.dispose();
}
Widget _buildBody() {
if (tickets == null) {
return SizedBox.shrink();
}
return ListView.builder(
itemCount: tickets.length <= 1 ? 1 : tickets.length,
itemBuilder: (BuildContext context, int i) {
if (tickets.length <= 0) {
return Container(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(S.of(context).no_ticket_yet),
),
);
} else {
Ticket ticket = tickets[i];
return GestureDetector(
onTap: () {
Routes.router.navigateTo(context, '/view-ticket/${ticket.id}');
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 0.6,
)
)
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
ticket.issue.msg,
style: TextStyle(
fontSize: 19.0,
),
overflow: TextOverflow.ellipsis,
),
),
Container(
child: Text(
Utils.utcDatetimeStringToLocalDatetimeString(context, ticket.createdAt, withTime: true),
style: TextStyle(
fontSize: 13.0,
color: Colors.grey,
),
),
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ticket.isClosed ?
Container(
padding: EdgeInsets.only(right: 10.0),
child: Icon(Icons.lock, color: Colors.green, size: 16.0,),
) :
SizedBox.shrink(),
Text(
S.of(context).followups_token(ticket.followUps.length),
style: TextStyle(
fontSize: 15.0,
color: Colors.black87,
),
),
],
),
)
],
),
),
],
),
),
);
}
}
);
}
@override
void initState() {
super.initState();
eventBus.on<OnTicketsUpdated>().listen((event) {
if (mounted) {
setState(() {
tickets = null;
});
_refreshController.requestRefresh();
}
});
}
void loadTicketes(bool isRefresh) {
_loadingFinish = false;
HttpUtil.httpGet(
'v1/mysupport',
businessId: widget.businessId,
queryParameters: {
'page': _page.toString(),
'size': Constants.TICKET_PER_PAGE_MOBILE.toString()
}
).then((value) {
if (mounted) {
if (isRefresh) {
_refreshController.refreshCompleted();
} else {
_refreshController.loadComplete();
}
if (int.parse(value['_meta']['currentPage'].toString()) >= int.parse(value['_meta']['pageCount'].toString())) {
_loadingFinish = true;
}
if (_loadingFinish) {
_refreshController.loadNoData();
}
_page = int.parse(value['_meta']['currentPage'].toString());
_pageCount = int.parse(value['_meta']['pageCount'].toString());
setState(() {
if (tickets == null) {
tickets = [];
}
tickets.addAll((value['tickets'] as List).map((e) => Ticket.fromJson(e)).toList());
});
}
}).catchError((error) {
if (mounted) {
if (isRefresh) {
_refreshController.refreshFailed();
} else {
_refreshController.loadFailed();
}
_isLoading = false;
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
}
});
}
}

View File

@@ -1,8 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/generated/l10n.dart';
import 'package:flutter_wisetronic/widgets/general/text_link.dart';
import 'package:flutter_wisetronic/widgets/mobile/mobile_navigation_drawer_header.dart';
import '../../routes.dart';
import '../../store/store.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/user.dart';
import '../../utils/utils.dart';
import '../../widgets/general/text_link.dart';
import '../../widgets/mobile/mobile_navigation_drawer_header.dart';
import '../../constants.dart';
@@ -17,6 +23,8 @@ class MobileNavigationDrawer extends StatefulWidget {
}
class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
User _user;
@override
Widget build(BuildContext context) {
String currentRoute = ModalRoute.of(context).settings.name;
@@ -53,18 +61,19 @@ class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
),
TextLink(
S.of(context).tutorials,
'/tutorials',
Constants.TUTORIAL_URL,
paddingVertical: 10.0,
paddingHorizontal: 15.0,
selected: currentRoute == '/tutorials',
closeDrawer: true,
isLink: true,
),
TextLink(
S.of(context).support,
'/support',
'/my-support/${Constants.BUSINESS_ID}',
paddingVertical: 10.0,
paddingHorizontal: 15.0,
selected: currentRoute == '/support',
selected: currentRoute == '/my-support/${Constants.BUSINESS_ID}',
closeDrawer: true,
),
TextLink(
@@ -77,12 +86,41 @@ class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
),
TextLink(
S.of(context).blog,
'/blog',
'/blog/${Constants.BUSINESS_ID}',
paddingVertical: 10.0,
paddingHorizontal: 15.0,
selected: currentRoute == '/blog',
selected: currentRoute == '/blog/${Constants.BUSINESS_ID}',
closeDrawer: true,
),
_user != null ?
(currentRoute == '/me') ?
GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
child: Text(
S.of(context).logout,
),
),
Icon(
Icons.logout,
)
],
),
onTap: () {
},
) : IconButton(
icon: Icon(
Icons.account_circle,
color: Colors.lightBlueAccent,
),
onPressed: () {
Routes.router.navigateTo(context, '/me');
},
) :
TextLink(
S.of(context).login,
'/login',
@@ -109,28 +147,28 @@ class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
),
TextLink(
S.of(context).service_policy,
'/service_policy',
'/service-policy',
paddingVertical: 5.0,
paddingHorizontal: 10.0,
closeDrawer: true,
),
TextLink(
S.of(context).return_policy,
'/return_policy',
'/return-policy',
paddingVertical: 5.0,
paddingHorizontal: 10.0,
closeDrawer: true,
),
TextLink(
S.of(context).privacy_policy,
'/privacy_policy',
'/privacy-policy',
paddingVertical: 5.0,
paddingHorizontal: 10.0,
closeDrawer: true,
),
TextLink(
S.of(context).license_agreement,
'/license_agreement',
'/end-user-license-agreement',
paddingVertical: 5.0,
paddingHorizontal: 10.0,
closeDrawer: true,
@@ -147,10 +185,10 @@ class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
),
),
TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).support_ticket, '/support_ticket', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).contact_us, '/contact_us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).about_us, '/about_us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).renew_license, '/renew_license', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).support_ticket, '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
Container(
margin: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Text(
@@ -238,10 +276,66 @@ class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
),
],
),
Container(
padding: EdgeInsets.only(left: 6.0, right: 6.0, top: 12.0, bottom: 18.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
child: Text(
'© 2007-${DateTime.now().year} wisetronic.com. All Rights Reserved.',
style: TextStyle(
fontSize: 10.0,
color: Colors.black38,
),
textAlign: TextAlign.center,
),
),
Container(
margin: EdgeInsets.only(top: 6.0),
child: Text(
'All logos shown are registered trademark, copyrighted and belong to their respective owners.',
style: TextStyle(
fontSize: 8.0,
color: Colors.black38,
),
textAlign: TextAlign.center,
),
),
],
),
),
],
),
),
);
}
@override
void initState() {
super.initState();
eventBus.on<OnCurrentUserUpdated>().listen((event) {
if (mounted) {
setState(() {
_user = store.state.user;
});
}
});
eventBus.on<OnGetCurrentUserFailed>().listen((event) {
if (mounted) {
setState(() {
_user = null;
});
}
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (mounted) {
setState(() {
_user = store.state.user;
});
}
});
}
}

View File

@@ -1,15 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/events/eventbus.dart';
import 'package:flutter_wisetronic/events/events.dart';
import 'package:flutter_wisetronic/widgets/general/navigationbar_logo.dart';
import '../../models/user.dart';
import '../../utils/utils.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../store/store.dart';
import '../../widgets/general/navigationbar_logo.dart';
import '../../routes.dart';
class MobileNavigationBar extends StatefulWidget {
final String title;
final bool back;
const MobileNavigationBar({Key key, this.title, this.back}) : super(key: key);
final bool toHome;
final bool showMe;
const MobileNavigationBar({Key key, this.title, this.back, this.toHome, this.showMe}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -19,6 +24,7 @@ class MobileNavigationBar extends StatefulWidget {
}
class MobileNavigationBarState extends State<MobileNavigationBar> {
User _user;
@override
Widget build(BuildContext context) {
@@ -30,7 +36,11 @@ class MobileNavigationBarState extends State<MobileNavigationBar> {
color: Colors.white,
),
onPressed: () {
Routes.router.pop(context);
if (widget.toHome) {
Routes.router.navigateTo(context, '/', clearStack: true);
} else {
Routes.router.pop(context);
}
},
) :
IconButton(
@@ -58,7 +68,53 @@ class MobileNavigationBarState extends State<MobileNavigationBar> {
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
),
centerTitle: true,
actions: actions(),
);
}
List<Widget> actions() {
List<Widget> ws = [];
// if (store.state.user != null && widget.showMe) {
// if (ModalRoute.of(context).settings.name != '/me') {
// ws.add(IconButton(
// icon: Icon(
// Icons.account_circle,
// color: Colors.white,
// ),
// onPressed: () {
// Routes.router.navigateTo(context, '/me');
// }
// ));
// }
// }
return ws;
}
@override
void initState() {
super.initState();
eventBus.on<OnCurrentUserUpdated>().listen((event) {
if (mounted) {
setState(() {
_user = store.state.user;
});
}
});
eventBus.on<OnGetCurrentUserFailed>().listen((event) {
if (mounted) {
setState(() {
_user = null;
});
}
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (mounted) {
setState(() {
_user = store.state.user;
});
}
});
}
}

View File

@@ -0,0 +1,395 @@
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:gender_selection/gender_selection.dart';
import '../../generated/l10n.dart';
import '../../models/located_address.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
class MobileNewAddress extends StatefulWidget {
final Key key;
final LocatedAddress locatedAddress;
final int businessId;
const MobileNewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileNewAddressState();
}
}
class MobileNewAddressState extends State<MobileNewAddress> {
final GlobalKey<FormState> _formKey = new GlobalKey();
final contactNameController = TextEditingController();
final phoneController = TextEditingController();
final streetLine1Controller = TextEditingController();
final streetLine2Controller = TextEditingController();
final cityController = TextEditingController();
final postalCodeController = TextEditingController();
final emailController = TextEditingController();
final faxController = TextEditingController();
String country = 'CA';
Gender _selectedGender;
String _selectedProvince;
List<String> provinces = <String>[
'Ontario',
'Quebec',
'British Columbia',
'Alberta',
'Manitoba',
'Saskatchewan',
'Nova Scotia',
'New Brunswich',
'Newfoundland and Labrador',
'Prince Edward Island',
'Northwest Territories',
'Nunavut',
'Yukon',
];
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).new_address),
backgroundColor: Theme.of(context).primaryColor,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: contactNameController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).contact_name,
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).contact_name_is_required;
}
return null;
},
),
),
GenderSelection(
maleText: S.of(context).mr,
femaleText: S.of(context).ms,
selectedGenderIconBackgroundColor: Colors.indigo,
checkIconAlignment: Alignment.bottomRight,
selectedGenderCheckIcon: Icons.check,
onChanged: (Gender gender) {
_selectedGender = gender;
},
equallyAligned: true,
animationDuration: Duration(milliseconds: 400),
isCircular: true,
isSelectedGenderIconCircular: true,
opacityOfGradient: 0.6,
padding: const EdgeInsets.all(3.0),
size: 50,
selectedGender: _selectedGender,
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
keyboardType: TextInputType.phone,
controller: phoneController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).mobile_phone_number,
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).mobile_phone_number_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: streetLine1Controller,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).street_line_1,
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).street_line_1_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: streetLine2Controller,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).street_line_2,
),
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: cityController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).city,
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).city_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: DropdownButton<String>(
value: _selectedProvince,
items: provinces.map((value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
if (mounted) {
setState(() {
_selectedProvince = newValue;
});
}
},
hint: Text(S.of(context).province),
isExpanded: true,
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 0.0),
child: TextFormField(
controller: postalCodeController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).postal_code,
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).postal_code_is_required;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 32.0, bottom: 0.0),
child: Text(
S.of(context).optional_information,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 0.0, bottom: 0.0),
child: TextFormField(
controller: emailController,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).email,
),
validator: (String value) {
if (value.isNotEmpty && !EmailValidator.validate(value)) {
return S.of(context).email_is_not_valid;
}
return null;
},
),
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: TextFormField(
controller: faxController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
)
),
labelText: S.of(context).fax,
),
),
),
],
),
),
),
bottomNavigationBar: Container(
padding: EdgeInsets.all(8.0),
color: Theme.of(context).buttonColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(),
FlatButton(
child: Text(
S.of(context).save
),
onPressed: () {
_saveNewAddress();
},
),
],
),
),
);
}
@override
void initState() {
super.initState();
setState(() {
if (widget.locatedAddress != null) {
if (provinces.contains(widget.locatedAddress.province)) {
_selectedProvince = widget.locatedAddress.province;
}
cityController.text = widget.locatedAddress.city;
postalCodeController.text = widget.locatedAddress.postalCode;
streetLine1Controller.text = (widget.locatedAddress.streetNumber != null
&& widget.locatedAddress.streetNumber.isNotEmpty
? widget.locatedAddress.streetNumber + ' ' : '')
+ widget.locatedAddress.streetName;
} else {
_selectedProvince = 'Ontario';
}
streetLine2Controller.text = '';
_selectedGender = Gender.Male;
});
}
void _saveNewAddress() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/addresses', (response) {
if (widget.businessId > 0) {
Routes.router.navigateTo(context, '/checkout/${widget.businessId}', replace: true);
} else {
Routes.router.navigateTo(context, '/my-addresses/${widget.businessId}', replace: true);
}
},
body: {
'name': contactNameController.text.trim(),
'address_line1': streetLine1Controller.text.trim(),
'address_line2': streetLine2Controller.text.trim(),
'city': cityController.text.trim(),
'state': _selectedProvince,
'zip': postalCodeController.text.trim(),
'phone': phoneController.text.trim(),
'gender': _selectedGender == Gender.Male ? 1 : 0,
'email': emailController.text,
'fax': faxController.text,
'country': country,
}
);
}
}
}

View File

@@ -0,0 +1,372 @@
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:smooth_star_rating/smooth_star_rating.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/comment.dart';
import '../../models/product_image.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
class MobileNewComment extends StatefulWidget {
final Key key;
final int orderId;
const MobileNewComment(this.orderId, {this.key});
@override
State<StatefulWidget> createState() {
return MobileNewCommentState();
}
}
class MobileNewCommentState extends State<MobileNewComment> {
Comment comment;
bool _showProgress;
double _progress;
double rating;
bool isSubmitting = false;
final TextEditingController commentController = new TextEditingController();
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).comment),
backgroundColor: Theme.of(context).primaryColor,
actions: <Widget>[
IconButton(
icon: Icon(Icons.check),
onPressed: () {
_submit();
},
),
],
),
body: _buildBody(context),
);
}
@override
void initState() {
super.initState();
_showProgress = false;
_progress = 0.0;
setState(() {
rating = 5.0;
});
eventBus.on<OnUploadCommentImageProgressEvent>().listen((event) {
if (mounted) {
setState(() {
_showProgress = event.showProgress;
_progress = event.progress;
});
}
});
eventBus.on<OnCommentUpdatedEvent>().listen((event) {
if (mounted) {
setState(() {
comment = event.comment;
});
}
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
Widget _buildBody(BuildContext context) {
return ListView.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int position) {
switch(position) {
case 0:
return Container(
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 16.0, bottom: 16.0),
child: SmoothStarRating(
allowHalfRating: false,
onRated: (v) {
setState(() {
rating = v;
});
},
starCount: 5,
rating: rating,
size: 40.0,
filledIconData: Icons.star,
color: Colors.green,
borderColor: Colors.green,
spacing: 0.0,
),
);
break;
case 1:
return Container(
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 16.0, right: 16.0),
child: TextField(
controller: commentController,
keyboardType: TextInputType.multiline,
maxLines: 10,
maxLength: 500,
decoration: new InputDecoration(
border: new OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(12.0),
),
),
filled: true,
hintStyle: new TextStyle(color: Colors.grey[800]),
hintText: S.of(context).input_your_comment,
fillColor: Colors.white70,
),
),
);
break;
case 2:
return Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: commentImages(context),
);
break;
}
return null;
}
);
}
Widget commentImages(BuildContext mainContext) {
Row row = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[],
);
if (comment != null && comment.images.length > 0) {
for (ProductImage image in comment.images) {
row.children.add(
Container(
padding: EdgeInsets.only(left: 10.0),
child: Badge(
position: BadgePosition.topEnd(top: -10.0, end: -6.0),
badgeContent: Container(
child: Text(
'-',
style: TextStyle(
fontSize: 14.0,
color: Colors.white,
),
),
),
child: GestureDetector(
child: Container(
child: Util.showImage('https:${image.image}',
width: 60.0,
height: 60.0,
fit: BoxFit.fill,
errorWidget: (mainContext, url, error) => Icon(
Icons.image,
size: 60.0,
color: Colors.grey,
),
),
),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).warning),
content: Text(S.of(context).are_you_sure_to_remove_the_picture),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).cancel),
onPressed: () {
Navigator.of(context).pop();
},
color: Theme.of(context).primaryColor,
),
FlatButton(
child: Text(S.of(context).yes_i_am_sure),
onPressed: () {
Navigator.of(context).pop();
_deleteImage(image.id);
},
),
],
);
}
);
},
),
),
),
);
}
}
row.children.add(
GestureDetector(
child: Container(
margin: EdgeInsets.only(left: 10,),
child: Icon(
Icons.add,
size: 60.0,
color: comment == null || comment.images.length < 3 ? Colors.lightBlue : Colors.black12,
),
decoration: BoxDecoration(
color: Colors.white70,
border: Border(
top: BorderSide(
width: 0.5,
color: Colors.black12,
),
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
left: BorderSide(
width: 0.5,
color: Colors.black12,
),
right: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
onTap: () {
if (comment == null || comment.images.length < 3) {
showDialog(
context: mainContext,
barrierDismissible: true,
builder: (BuildContext context) {
return Util().getPicture(mainContext, store.state.user,
commentId: comment != null ? comment.id : 0,
orderId: widget.orderId);
}
);
}
},
),
);
return Stack(
children: <Widget>[
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
bottom: -100.0,
child: Visibility(
visible: _showProgress,
child: LinearPercentIndicator(
lineHeight: 10.0,
percent: _progress,
backgroundColor: Colors.transparent,
progressColor: Colors.blue,
linearStrokeCap: LinearStrokeCap.butt,
),
),
),
Positioned(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(bottom: 16.0),
child: Text(
S.of(context).add_pictures,
style: TextStyle(
fontSize: 17.0,
color: Colors.black38,
),
),
),
row,
],
),
),
],
);
}
void _deleteImage(int imageId) {
HttpUtil.httpDelete('v1/comment-image/${imageId}', (response) {
if (mounted) {
setState(() {
comment = Comment.fromJson(response.data);
});
}
},
queryParameters: {
'comment_id': comment != null ? comment.id : 0,
},
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
void _submit() {
if (commentController.text.isEmpty) {
Utils.showMessageDialog(context, Exception(S.of(context).comment_empty));
} else {
HttpUtil.httpPost('v1/add-comment', (response) {
if (isSubmitting) {
isSubmitting = false;
Navigator.of(context).pop();
}
Utils.showMessageDialog(context,
Exception(S.of(context).thank_you_for_your_comment),
title: S.of(context).success,
onOk: () {
Routes.router.navigateTo(context, '/orders', replace: true, clearStack: true);
}
);
},
isFormData: true,
body: {
'order_id': widget.orderId,
'comment_id': comment != null ? comment.id : 0,
'content': commentController.text,
'rating': rating.round(),
},
).catchError((error) {
if (isSubmitting) {
isSubmitting = false;
Navigator.of(context).pop();
}
Utils.showMessageDialog(context, error);
});
isSubmitting = true;
Utils.showSubmitDialog(context);
}
}
}

View File

@@ -0,0 +1,400 @@
import 'package:badges/badges.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_wisetronic/events/eventbus.dart';
import 'package:flutter_wisetronic/events/events.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import '../../models/user.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../constants.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
class MobileNewTicket extends StatefulWidget {
final Key key;
final int businessId;
const MobileNewTicket({this.key, int businessId}) :
businessId = businessId ?? Constants.BUSINESS_ID;
@override
State<StatefulWidget> createState() {
return MobileNewTicketState();
}
}
class MobileNewTicketState extends State<MobileNewTicket> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final issueMsgController = TextEditingController();
bool onSubmitting = false;
double submitProgress = 0.0;
final List<Map<String, dynamic>> galleryImagesFlag = [
{'show': false, 'progress': 0.0, 'path': ''},
{'show': false, 'progress': 0.0, 'path': ''},
{'show': false, 'progress': 0.0, 'path': ''},
];
@override
void initState() {
super.initState();
onSubmitting = false;
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
BuildContext mainContext = context;
Widget view = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0),
child: Text(
S.of(context).add_new_ticket,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0),
child: Text(
S.of(context).add_new_ticket_desc,
style: TextStyle(
color: Colors.black54,
fontSize: 14.0,
),
),
),
Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: TextFormField(
controller: issueMsgController,
keyboardType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences,
decoration: new InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 1.0,
color: Colors.blue,
),
),
labelText: S.of(context).your_question_issue,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).this_field_is_required;
}
return null;
},
maxLines: 5,
maxLength: 1000,
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 10.0,
color: Colors.black26,
),
),
),
),
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 4.0),
child: Text(
S.of(context).attach_pictures,
style: TextStyle(
fontSize: 17.0,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 16.0, bottom: 8.0),
child: Text(
S.of(context).attach_pictures_desc,
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
),
),
Container(
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0),
child: galleryImages(mainContext),
),
],
),
),
Align(
alignment: Alignment.centerRight,
child: Container(
padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0),
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).submit,
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {
_submit();
},
),
),
),
],
);
return SingleChildScrollView(
child: Stack(
children: [
Visibility(
visible: onSubmitting,
child: Container(
height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.bottom - MediaQuery.of(context).padding.top,
color: Colors.grey.withOpacity(0.6),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircularProgressIndicator(
strokeWidth: 10,
backgroundColor: Colors.green,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),
value: submitProgress,
),
Container(
margin: EdgeInsets.only(top: 16.0),
child: Text(
S.of(context).submitting_please_wait,
style: TextStyle(
color: Colors.white,
),
),
),
],
),
),
),
),
Visibility(
visible: !onSubmitting,
child: view,
),
],
),
);
}
void _submit() {
final FormState form = _formKey.currentState;
if (form.validate()) {
setState(() {
onSubmitting = true;
});
Util().createTicket(context, issueMsgController.text.trim(),
galleryImagesFlag, (response){
showDialog(context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).success),
content: Text(S.of(context).ticket_created_success),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).ok),
onPressed: () {
Routes.router.navigateTo(context, '/my-support/${widget.businessId}', replace: true);
},
),
],
);
});
}, (error) {
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.pop(context);
Routes.router.pop(context);
});
}, id: null);
}
}
Widget galleryImages(BuildContext mainContext) {
Row row = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[],
);
for (int i = 0; i < 3; i++) {
Map<String, dynamic> image = galleryImagesFlag[i];
Widget imageWidget;
imageWidget = GestureDetector(
child: Stack(
alignment: Alignment.center,
children: [
Container(
margin: EdgeInsets.only(right: 20.0),
child: Util.showImage(
'${image['path']}',
width: 80.0,
height: 80.0,
fit: BoxFit.fill
),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 0.5,
color: Colors.black12,
),
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
left: BorderSide(
width: 0.5,
color: Colors.black12,
),
right: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
Visibility(
visible: galleryImagesFlag[i]['show'],
child: Container(
padding: EdgeInsets.only(right: 20.0),
child: CircularPercentIndicator(
radius: 60.0,
lineWidth: 10.0,
percent: galleryImagesFlag[i]['progress'],
center: new Text(
'${(galleryImagesFlag[i]['progress'] * 100.0).toStringAsFixed(1)}%',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15.0,
color: Colors.white,
),
),
circularStrokeCap: CircularStrokeCap.round,
progressColor: Colors.orange,
),
),
),
],
),
onTap: () {
showDialog(
context: mainContext,
builder: (BuildContext context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
return Util().getPicture2(mainContext, i, (int imageId, String path){
setState(() {
galleryImagesFlag[imageId]['path'] = path;
});
});
return null;
}
);
},
);
row.children.add(Badge(
position: BadgePosition.topEnd(top: -10.0, end: 10.0),
badgeContent: Container(
child: GestureDetector(
child: Icon(
Icons.clear,
color: Colors.white,
size: 14.0,
),
onTap: () {
_deleteImage(mainContext, i);
},
),
),
showBadge: image['path'].isNotEmpty ? true : false,
child: imageWidget,
));
}
return row;
}
void _deleteImage(BuildContext mainContext, int imageId) {
showDialog(context: mainContext,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).warning),
content: Text(S.of(context).are_you_sure_to_remove_the_picture),
actions: [
FlatButton(
child: Text(S.of(context).cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
RaisedButton(
child: Text(S.of(context).yes_i_am_sure),
color: Theme.of(context).primaryColor,
onPressed: () {
Navigator.of(context).pop();
setState(() {
galleryImagesFlag[imageId]['path'] = '';
});
},
),
],
);
},
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
eventBus.on<OnSubmitProgressEvent>().listen((event) {
if (mounted) {
setState(() {
onSubmitting = event.showProgress;
submitProgress = event.progress;
});
}
});
}
}

View File

@@ -0,0 +1,319 @@
import 'package:countdown/countdown.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../constants.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileNewUser extends StatefulWidget {
const MobileNewUser({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileNewUserState();
}
}
class MobileNewUserState extends State<MobileNewUser> {
final GlobalKey<FormState> _formKey = GlobalKey();
final usernameController = TextEditingController();
bool usernameEnable = true;
final codeController = TextEditingController();
bool enableGetCode;
String getCodeText;
bool canRegister;
var countDownListener;
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
S.of(context).user_registration,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
padding: EdgeInsets.only(top: 8.0, bottom: 16.0),
child: Text(
S.of(context).user_registration_desc,
style: TextStyle(
color: Colors.black38,
),
),
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: usernameController,
enabled: usernameEnable,
keyboardType: TextInputType.emailAddress,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).mobile_or_email,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).mobile_or_email_is_required;
}
return null;
},
onChanged: (string) {
if (string.isEmpty) {
if (mounted) {
setState(() {
canRegister = false;
});
}
} else if (string.isNotEmpty && codeController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canRegister = true;
});
}
}
if (string.isNotEmpty && !enableGetCode) {
if (mounted) {
setState(() {
enableGetCode = true;
});
}
} else if (string.isEmpty && enableGetCode) {
if (mounted) {
setState(() {
enableGetCode = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: codeController,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).verification_code,
suffixIcon: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Container(
margin: EdgeInsets.only(top: 6.0),
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: new Border.all(
color: enableGetCode ? Colors.black87 : Colors.black26,
width: 1.0,
),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Text(
getCodeText,
style: TextStyle(
color: enableGetCode ? Colors.black87 : Colors.black26,
fontSize: 14.0
),
),
),
),
onTap: enableGetCode ? getCodeTapped : null,
),
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).verification_code_is_required;
}
return null;
},
onChanged: (string) {
if (usernameController.text.trim().isNotEmpty && string.isNotEmpty) {
if (mounted) {
setState(() {
canRegister = true;
});
}
} else {
if (mounted) {
setState(() {
canRegister = false;
});
}
}
},
),
),
],
),
),
),
Container(
margin: EdgeInsets.only(top: 0.0, right: 16.0),
child: Align(
alignment: Alignment.centerRight,
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).register,
style: TextStyle(
color: Colors.white,
),
),
onPressed: canRegister ? register : null,
),
),
),
],
);
}
@override
void initState() {
super.initState();
setState(() {
enableGetCode = false;
canRegister = false;
usernameEnable = true;
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
getCodeText = S.of(context).get_code;
}
void register() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/users', (response) {
Routes.router.navigateTo(context, '/set-password/${usernameController.text.trim()}/${codeController.text.trim()}', replace: true);
},
queryParameters: {
'action': 'verify_code'
},
isFormData: true,
body: {
'mobile': usernameController.text.trim(),
'code': codeController.text.trim(),
'store_id': Constants.BUSINESS_ID
},
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}
void getCodeTapped() {
if (usernameController.text.isNotEmpty) {
HttpUtil.httpPost('v1/users', (response) {
Fluttertoast.showToast(
msg: S.of(context).verification_code_sent,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black54,
textColor: Colors.white
);
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
startCountDown();
if (mounted) {
setState(() {
usernameEnable = false;
});
}
},
queryParameters: {'action': 'send_code'},
body: {
'mobile': usernameController.text,
},
isFormData: true,
).catchError((error) {
if (mounted) {
setState(() {
canRegister = false;
});
}
Utils.showMessageDialog(context, error);
});
} else {
Fluttertoast.showToast(
msg: S.of(context).enter_mobile_or_email,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.red,
textColor: Colors.white
);
}
}
void startCountDown() {
countDownListener.onData((Duration d) {
if (mounted) {
setState(() {
enableGetCode = false;
getCodeText = S.of(context).get_code_token(d.inSeconds);
});
}
});
countDownListener.onDone(() {
if (mounted) {
setState(() {
enableGetCode = true;
getCodeText = S.of(context).get_code_again;
});
}
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,452 @@
import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/events/eventbus.dart';
import 'package:flutter_wisetronic/events/events.dart';
import 'package:flutter_wisetronic/generated/l10n.dart';
import 'package:flutter_wisetronic/models/order.dart';
import 'package:flutter_wisetronic/pages/order_detail.dart';
import 'package:flutter_wisetronic/store/actions.dart';
import 'package:flutter_wisetronic/store/store.dart';
import 'package:flutter_wisetronic/utils/double_back_to_close_app.dart';
import 'package:flutter_wisetronic/utils/http_util.dart';
import 'package:flutter_wisetronic/utils/utils.dart';
import '../../constants.dart';
import '../../routes.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'MobileBottomNav.dart';
class MobileOrders extends StatefulWidget {
final Key key;
const MobileOrders({this.key});
@override
State<StatefulWidget> createState() {
return MobileOrdersState();
}
}
class MobileOrdersState extends State<MobileOrders> with SingleTickerProviderStateMixin {
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
List<Order> orders = [];
int _page = 1;
int _pageCount = 1;
bool _isLoading = false;
bool _loadingFinish = false;
RefreshController _refreshController = RefreshController(initialRefresh: true);
void _onRefresh() {
_page = 1;
if (orders != null) {
orders.clear();
} else {
orders = [];
}
_refreshController.resetNoData();
_loadData(true);
}
void _onLoadMore() {
// if failed,use loadFailed(),if no data return,use LoadNodata()
if (_pageCount > _page) {
_page += 1;
_loadData(false);
} else {
_refreshController.loadNoData();
}
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (store.state.user == null) {
store.dispatch(UpdateRedirectRoute('/orders'));
Routes.router.navigateTo(context, '/login', replace: true);
return;
}
});
Widget body = SmartRefresher(
enablePullDown: true,
enablePullUp: true,
header: WaterDropHeader(),
footer: CustomFooter(
builder: (BuildContext context, LoadStatus mode){
Widget footer;
if(mode == LoadStatus.idle) {
footer = Text(S.of(context).pull_up_to_load_more);
} else if (mode == LoadStatus.loading) {
footer = CircularProgressIndicator();
} else if (mode == LoadStatus.failed) {
footer = Text(S.of(context).load_failed_retry);
} else if (mode == LoadStatus.canLoading) {
footer = Text(S.of(context).release_to_load_more);
} else if (mode == LoadStatus.noMore) {
footer = Text(S.of(context).no_more_record);
} else {
footer = Text('...');
}
return Container(
height: 55.0,
child: Center(child: footer,),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoadMore,
child: _buildBody(),
);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(
S.of(context).my_orders,
),
centerTitle: true,
),
body: DoubleBackToCloseApp(
snackBar: SnackBar(
content: Text(S.of(context).tap_back_again_to_exit),
),
child: body,
),
bottomNavigationBar: MobileBottomNav(currentIndex: 3,),
);
}
Widget _buildBody() {
return ListView.builder(
itemCount: orders != null && orders.length > 0 ? orders.length : 1,
itemBuilder: (BuildContext context, int position) {
Order order;
if (orders != null && orders.length > 0) {
order = orders[position];
}
if (order == null) {
return Container(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(
S.of(context).you_have_no_orders_yet,
),
),
);
}
Row row = Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[],
);
row.children.add(Expanded(
child: Container(
child: Text(
order.cartInfo.productList[0].name,
style: TextStyle(
fontSize: 14.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
));
if (order.cartInfo.productList.length > 1) {
row.children.add(Container(
child: Text(
S.of(context).and_more_item_token(Utils.getProductLineInOrder(order.cartInfo)),
style: TextStyle(
fontSize: 12.0,
color: Colors.black26,
),
),
));
}
row.children.add(Container(
width: 80.0,
alignment: Alignment.centerRight,
child: Text(
'\$${order.totalPrice.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
));
Row row3 = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
RaisedButton(
child: Text(
S.of(context).detail,
),
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,),
));
},
),
],
);
if (order.status == Constants.STATUS_COMPLETE && !order.hasComment) {
row3.children.add(
Padding(
padding: EdgeInsets.only(left: 10.0),
child: RaisedButton(
child: Text(
S.of(context).comment,
),
onPressed: () {
Routes.router.navigateTo(context, '/new-comment/${order.id}');
},
),
),
);
}
Row row2 = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[],
);
if (order.paymentStatus != Constants.PAYMENT_STATUS_PAID) {
row2.children.add(row3);
if (order.paymentStatus != Constants.PAYMENT_STATUS_PAID
&& order.status != Constants.STATUS_CANCELLED
&& order.status != Constants.STATUS_COMPLETE) {
row2.children.add(RaisedButton(
child: Text(
S.of(context)
.pay_now,
style: TextStyle(
color: Colors.white,
),
),
color: Colors.redAccent,
onPressed: () {
Routes.router.navigateTo(context, '/paynow/${order.id}');
},
));
} else {
row2.children.add(RaisedButton(
child: Text(
S.of(context).order_again,
style: TextStyle(
color: Colors.white,
),
),
color: Theme.of(context).primaryColor,
onPressed: () {
Utils.orderAgain(context, order.cartInfo);
},
));
}
} else {
row2.children.add(row3);
row2.children.add(RaisedButton(
child: Text(
S.of(context).order_again,
style: TextStyle(
color: Colors.white,
),
),
color: Theme.of(context).primaryColor,
onPressed: () {
Utils.orderAgain(context, order.cartInfo);
},
));
}
return Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 10.0,
color: Colors.black26,
),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Util.showImage('${order.cartInfo.businessInfo.picUrl}',
width: 32.0,
height: 32.0,
fit: BoxFit.fill,
),
),
Expanded(
child: GestureDetector(
child: Container(
margin: EdgeInsets.only(left: 5.0, right: 5.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'${order.cartInfo.businessInfo.name}',
style: TextStyle(
fontSize: 20.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Container(
child: Text(
'#${order.orderNum} ${Utils.timestampToString(context, order.createdAt, withTime: true)}',
style: TextStyle(
fontSize: 12.0,
color: Colors.black26,
),
),
),
],
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,),
));
},
),
),
Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
'${Utils.getOrderStatus(context, order.status)}',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
),
),
Container(
child: SizedBox.shrink(),
),
],
),
),
],
),
Container(
padding: EdgeInsets.only(top: 10.0, bottom: 10.0),
width: double.infinity,
child: SizedBox(),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
GestureDetector(
child: Container(
margin: EdgeInsets.only(top: 10.0, bottom: 20.0),
child: row,
),
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => OrderDetail(order.id, fromOrders: true,),
));
},
),
Container(
child: row2,
),
],
),
);
},
);
}
@override
void initState() {
super.initState();
_page = 1;
_pageCount = 1;
eventBus.on<OnOrderUpdated>().listen((event) {
if (mounted) {
setState(() {
orders = null;
_refreshController.requestRefresh();
});
}
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
_loadData(bool isRefresh) {
_loadingFinish = false;
HttpUtil.httpGet('v1/orders',
queryParameters: {
'expand': 'cart_info',
'page': _page.toString(),
'size': Constants.ORDERS_PER_PAGE.toString(),
},
).then((data) {
// Utils.jsonPrettyPrint(data);
if (isRefresh) {
_refreshController.refreshCompleted();
} else {
_refreshController.loadComplete();
}
if (int.parse(data['_meta']['currentPage'].toString()) >= int.parse(data['_meta']['pageCount'].toString())) {
_loadingFinish = true;
}
if (_loadingFinish) {
_refreshController.loadNoData();
}
_page = int.parse(data['_meta']['currentPage'].toString());
_pageCount = int.parse(data['_meta']['pageCount'].toString());
if (mounted) {
setState(() {
_isLoading = false;
orders.addAll((data['items'] as List).map((e) => Order.fromJson(e)).toList());
});
}
}).catchError((error) {
if (isRefresh) {
_refreshController.refreshFailed();
} else {
_refreshController.loadFailed();
}
if(mounted) {
setState(() {
_isLoading = false;
});
}
Utils.showMessageDialog(context, error);
});
}
}

View File

@@ -0,0 +1,377 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/order.dart';
import '../../models/payment_platform.dart';
import '../../models/stripe_payment_method.dart';
import '../../models/user.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
import '../../widgets/general/payment_verification_code_dialog.dart';
class MobilePayNow extends StatefulWidget {
final Key key;
final int orderId;
const MobilePayNow(this.orderId, {this.key});
@override
State<StatefulWidget> createState() {
return MobilePayNowState();
}
}
class MobilePayNowState extends State<MobilePayNow> {
Order order;
List<PaymentPlatform> paymentPlatforms;
User _user;
bool nativePay;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (order == null ) {
return new Scaffold(
body: Center(
child: SpinKitWave(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
BuildContext mainContext = context;
ListView listView = ListView.builder(
itemCount: nativePay ? paymentPlatforms.length + 3 : paymentPlatforms.length + 2,
itemBuilder: (BuildContext context, int position) {
if (position == 0) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.only(top: 32.0, right: 16.0, left: 16.0, bottom: 32.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
S.of(context).payment_amount,
style: TextStyle(
fontSize: 15.0,
color: Colors.black26,
),
),
Text(
'\$${order.totalPrice.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 10.0,
color: Colors.black26,
)
)
),
),
store.state.deviceId != null && store.state.deviceId.isNotEmpty || store.state.tableNumber != null && store.state.tableNumber.isNotEmpty ?
GestureDetector(
child: Container(
padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
Icons.local_cafe,
size: 50.0,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 20.0, right: 10.0),
child: Text(
S.of(context).pay_later,
style: TextStyle(
fontSize: 20.0,
),
),
),
Container(
margin: EdgeInsets.only(left: 20.0, right: 10.0),
child: Text(
S.of(context).pay_after_meal,
style: TextStyle(
fontSize: 14.0,
color: Colors.black26,
),
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
color: Colors.black26,
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 10.0,
color: Colors.black26,
)
)
),
),
onTap: () {
Routes.router.navigateTo(context, '/orders', replace: true, clearStack: true);
},
) :
SizedBox.shrink()
],
);
}
PaymentPlatform paymentPlatform;
if (position == 1) {
if (_user.stripePaymentMethods.length > 0) {
paymentPlatform = paymentPlatforms[position - 1];
Column column = Column(
children: <Widget>[],
);
column.children.add(
Container(
padding: EdgeInsets.all(16.0),
width: double.infinity,
child: Text(
S.of(context).pay_with_existing_cards,
textAlign: TextAlign.left,
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Colors.black26,
),
),
),
),
);
for (StripePaymentMethod stripePaymentMethod in _user.stripePaymentMethods) {
column.children.add(
GestureDetector(
child: Container(
padding: EdgeInsets.only(
top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
stripePaymentMethod.cardBrand == 'visa' ?
Image.asset(
'assets/images/visa.png',
width: 50.0,
height: 50.0,
fit: BoxFit.fill,
) : (stripePaymentMethod.cardBrand == 'mastercard' ?
Image.asset(
'assets/images/master.png',
width: 50.0,
height: 50.0,
fit: BoxFit.fill,
) : Icon(
Icons.credit_card, size: 50.0, color: Colors.black38,)),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 20.0, right: 10.0),
child: Text(
'**** ${stripePaymentMethod.cardLast4}',
style: TextStyle(
fontSize: 20.0,
),
),
),
Container(
margin: EdgeInsets.only(left: 20.0, right: 10.0),
child: Text(
S.of(context).expire_token(stripePaymentMethod.cardExpMonth, stripePaymentMethod.cardExpYear),
style: TextStyle(
fontSize: 14.0,
color: Colors.black26,
),
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
color: Colors.black26,
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
onTap: () {
showDialog(
context: mainContext,
builder: (BuildContext context) {
return PaymentVerificationCodeDialog(_user, () {
Util.goPayment(context, order, paymentPlatform,
stripePaymentMethod: stripePaymentMethod);
}, () {
});
},
);
},
),
);
}
column.children.add(
Container(
width: double.infinity,
child: SizedBox.shrink(),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 10,
color: Colors.black26,
),
),
),
)
);
return column;
} else {
return SizedBox.shrink();
}
}
if (position == 2 && nativePay) {
paymentPlatform = paymentPlatforms[position - 2];
return Util().getNativePay(mainContext, order, paymentPlatform);
}
paymentPlatform = nativePay ? paymentPlatforms[position - 3] : paymentPlatforms[position - 2];
return GestureDetector(
child: Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 10.0),
child: Util.showImage(paymentPlatform.icon,
errorWidget: (context, url, error) => Icon(Icons.broken_image, size: 50.0, color: Colors.transparent,),
fit: BoxFit.cover,
width: 50.0,
height: 50.0,
),
),
Expanded(
child: Text(
S.of(context).pay_with_token(PaymentPlatform.getPaymentPlatformName(context, paymentPlatform.code)),
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
),
Icon(
Icons.arrow_forward_ios,
color: Colors.black26,
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 10,
color: Colors.black26,
),
),
),
),
onTap: () {
Util.goPayment(context, order, paymentPlatform);
},
);
}
);
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).pay_now),
backgroundColor: Theme.of(context).primaryColor,
),
body: listView,
);
}
@override
void initState() {
super.initState();
HttpUtil.httpGet(
'v1/${widget.orderId}/paymentplatforms',
).then((data) {
paymentPlatforms = (data['payment_platforms'] as List).map((e) =>
PaymentPlatform.fromJson(e)).toList();
PaymentPlatform pp = paymentPlatforms[0];
if (Constants.ENABLE_NATIVE_PAY && pp.publishableKey != null
&& pp.publishableKey.isNotEmpty && pp.merchantId != null
&& pp.merchantId.isNotEmpty) {
nativePay = true;
} else {
nativePay = false;
}
_user = User.fromJson(data['contact']);
store.dispatch(UpdateCurrentUser(_user));
eventBus.fire(OnCurrentUserUpdated());
setState(() {
order = Order.fromJson(data['order']);
});
}).catchError((error) {
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.navigateTo(context, "/orders", replace: true);
});
});
}
}

View File

@@ -0,0 +1,63 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import '../../models/blog.dart';
class MobilePlainPage extends StatefulWidget {
final Blog blog;
const MobilePlainPage(this.blog, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobilePlainPageState();
}
}
class MobilePlainPageState extends State<MobilePlainPage> {
@override
Widget build(BuildContext context) {
Column col = Column(
children: [],
);
col.children.add(Container(
padding: EdgeInsets.only(top: 20, left: 16.0, right: 16.0, bottom: 16.0),
child: Center(
child: Text(
widget.blog.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
));
col.children.add(Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 20.0),
child: MarkdownBody(
shrinkWrap: true,
data: widget.blog.body,
),
));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(widget.blog.title),
backgroundColor: Theme.of(context).primaryColor,
),
body: SingleChildScrollView(
child: col,
),
);
}
}

View File

@@ -0,0 +1,477 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/Subproduct.dart';
import '../../models/business.dart';
import '../../models/product.dart';
import '../../models/product_detail.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/shop_scroll_controller.dart';
import '../../utils/shop_scroll_coordinator.dart';
import '../../utils/util_web.dart'
if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
import '../../widgets/general/add_remove_button.dart';
import '../../widgets/general/carousel.dart';
import '../../widgets/general/show_price.dart';
MediaQueryData mediaQuery;
double statusBarHeight;
double screenHeight;
double screenWidth;
class MobileProductDetailPage extends StatefulWidget {
final Business business;
final Product product;
const MobileProductDetailPage(
{@required this.business, @required this.product, Key key})
: super(key: key);
@override
State<StatefulWidget> createState() {
return MobileProductDetailPageState();
}
}
class MobileProductDetailPageState extends State<MobileProductDetailPage>
with SingleTickerProviderStateMixin {
ShopScrollCoordinator _shopCoordinator;
ShopScrollController _pageScrollController;
TabController _tabController;
double _sliverAppBarInitHeight;
double _sliverAppBarMaxHeight;
final double _tabBarHeight = 50;
ProductDetail productDetail;
bool refresh;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
if (productDetail == null) {
return new Scaffold(
body: Center(
child: SpinKitWave(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
refresh = false;
mediaQuery ??= MediaQuery.of(context);
screenHeight ??= mediaQuery.size.height;
screenWidth ??= mediaQuery.size.width;
statusBarHeight ??= mediaQuery.padding.top;
_sliverAppBarInitHeight ??= screenWidth;
_sliverAppBarMaxHeight ??= screenWidth;
_pageScrollController ??= _shopCoordinator
.pageScrollController(_sliverAppBarMaxHeight - _sliverAppBarInitHeight);
_shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () {
return statusBarHeight + kToolbarHeight + _tabBarHeight;
};
return Scaffold(
body: Listener(
onPointerUp: _shopCoordinator.onPointerUp,
child: CustomScrollView(
controller: _pageScrollController,
physics: ClampingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
pinned: true,
floating: true,
snap: true,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: Colors.black26,
),
onPressed: () {
Navigator.of(context).pop();
},
),
backgroundColor: Theme.of(context).primaryColor,
expandedHeight: _sliverAppBarMaxHeight - 30.0,
flexibleSpace: FlexibleSpaceBar(
background: Carousel(
height: _sliverAppBarInitHeight + 0.0,
pages: _getProductPictures(context),
autoPlay: false,
),
),
),
SliverPersistentHeader(
pinned: false,
floating: true,
delegate: _SliverAppBarDelegate(
minHeight: 60.0,
maxHeight: 60.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding:
EdgeInsets.only(left: 10.0, top: 5.0, right: 10.0),
child: Text(
productDetail.name,
style: TextStyle(
fontSize: 15.0, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ShowPrice(
productDetail.price,
currencySign: '\$',
fontWeight: FontWeight.bold,
smallFontSize: 14,
largeFontSize: 18,
regularPrice: productDetail.regularPrice,
),
Container(
child: AddRemoveButton(
business: widget.business,
product: widget.product,
),
padding: EdgeInsets.only(top: 5.0, right: 10.0),
),
],
)
],
),
),
),
SliverPersistentHeader(
pinned: true,
floating: false,
delegate: _SliverAppBarDelegate(
minHeight: _tabBarHeight,
maxHeight: _tabBarHeight,
child: Container(
color: Colors.white,
child: TabBar(
labelColor: new Color(0xFF3190E8),
unselectedLabelColor: new Color(0xFF666666),
indicatorColor: new Color(0xFF3190E8),
indicatorSize: TabBarIndicatorSize.label,
labelStyle: new TextStyle(
fontSize: 16.0,
),
controller: _tabController,
tabs: <Widget>[
Tab(
text: S.of(context).detail,
),
Tab(
text: S.of(context).specification,
),
],
),
),
),
),
SliverFillRemaining(
child: TabBarView(
controller: _tabController,
children: <Widget>[
SingleChildScrollView(
controller: _shopCoordinator.newChildScrollController(),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
(productDetail.subproducts.length > 0) ?
subProducts(productDetail.subproducts) :
SizedBox.shrink(),
Container(
padding: EdgeInsets.only(
top: 10.0, left: 10.0, right: 10.0),
child: Text(
productDetail.description,
style: TextStyle(
fontSize: 14.0, color: Colors.black54),
),
),
Container(
padding: EdgeInsets.only(left: 10.0, right: 10.0),
child: (productDetail.description2 != null &&
!productDetail.description2.isEmpty)
? Text(
'${productDetail.description2}',
style: TextStyle(
fontSize: 14.0, color: Colors.black54),
)
: SizedBox.shrink(),
),
productDetail.detailDescription != null
? Container(
padding:
EdgeInsets.only(left: 10.0, right: 10.0),
child: MarkdownBody(
data: '${productDetail.detailDescription}',
shrinkWrap: true,
),
)
: SizedBox.shrink(),
],
),
),
),
Container(
padding: EdgeInsets.only(
top: 10.0, left: 10.0, right: 10.0, bottom: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
S.of(context).weight_token(productDetail.weight),
style: TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
),
Container(
child: Text(
S.of(context).dimentions_token(
productDetail.dimentionsLength,
productDetail.dimentionsWidth,
productDetail.dimentionsHeight),
style: TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
)
],
),
)
],
),
)
],
),
),
);
}
@override
void initState() {
setState(() {
refresh = false;
});
super.initState();
_shopCoordinator = ShopScrollCoordinator();
_tabController = TabController(vsync: this, length: 2);
HttpUtil.httpGet(
'v1/product-detail/${widget.product.id}',
businessId: widget.business.id,
).then((value) {
productDetail = ProductDetail.fromJson(value);
setState(() {
refresh = true;
});
}).catchError((error) {
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
});
eventBus.on<OnCartInfoUpdated>().listen((event) {
if (mounted) {
setState(() {
refresh = true;
});
}
});
}
Widget subProducts(List<Subproduct> subproducts) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(left: 6, top: 10, bottom: 10),
child: Text(
S.of(context).includes,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
)
],
);
for (int i = 0; i < subproducts.length; i++) {
col.children.add(subProduct(subproducts[i]));
}
return col;
}
Widget subProduct(Subproduct subproduct) {
Row row = Row(
children: [],
);
row.children.add(
Container(
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 5.0),
child: Util.showImage(
'https:${subproduct.product.image}',
width: 48,
height: 48,
fit: BoxFit.contain,
),
),
);
row.children.add(
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Container(
padding: EdgeInsets.only(left: 12, top: 5),
child: Text(
subproduct.product.name,
style: TextStyle(
fontSize: 15.0,
),
),
),
),
Container(
width: 80,
padding: EdgeInsets.only(left: 12, top: 5, right: 12),
child: Text(
'${subproduct.product.price.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 13,
decoration: TextDecoration.lineThrough,
),
),
alignment: Alignment.centerRight,
),
Container(
width: 60,
padding: EdgeInsets.only(left: 12, top: 5, right: 12),
child: Text(
'x${subproduct.quantity.toStringAsFixed(0)}',
style: TextStyle(
fontSize: 13,
),
),
alignment: Alignment.centerRight,
),
],
),
Container(
padding: EdgeInsets.only(left: 12, top: 12, right: 12),
child: Text(
'${subproduct.product.description}',
style: TextStyle(
fontSize: 12,
color: Colors.black45,
),
),
),
],
),
),
);
return Container(
padding: EdgeInsets.only(left: 0, right: 0, top: 0, bottom: 0),
child: row,
);
}
List<Widget> _getProductPictures(BuildContext context) {
var pages = <Widget>[];
List<String> images = [];
images.add(productDetail.image);
for (var i = 0; i < productDetail.images.length; i++) {
// print('>>https:' + productDetail.images[i].image);
images.add(productDetail.images[i].image);
}
for (var i = 0; i < images.length; i++) {
pages.add(new GestureDetector(
child: new Container(
child: Util.showImage(
'https:' + images[i],
),
),
onTap: () {},
));
}
return pages;
}
@override
void dispose() {
_tabController?.dispose();
_pageScrollController?.dispose();
super.dispose();
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => this.minHeight;
@override
double get maxExtent => max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}

View File

@@ -0,0 +1,141 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/product.dart';
import '../../pages/product_detail_page.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../widgets/general/add_remove_button.dart';
import '../../widgets/general/show_price.dart';
import '../../widgets/general/style.dart';
class MobileProductItem extends StatefulWidget {
final Product product;
final Business business;
MobileProductItem({this.product, this.business, Key key})
: super(key: key);
@override
State<StatefulWidget> createState() {
return MobileProductItemState();
}
}
class MobileProductItemState extends State<MobileProductItem> {
int _qty = 0;
@override
Widget build(BuildContext context) {
return new Container(
height: 124.0,
padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 15.0).copyWith(bottom: 5.0),
decoration: new BoxDecoration(
color: Style.backgroundColor,
border: new Border(
bottom: new BorderSide(
color: new Color(0xFFEBEBEB),
)
),
),
child: new SizedBox.expand(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: new EdgeInsets.only(right: 10.0),
width: 80.0,
height: 80.0,
child: GestureDetector(
child: Util.showImage('${widget.product.imagePath}',
fit: BoxFit.fill,
),
onTap: (){
_showProductDetail();
},
),
),
new Expanded(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
child: GestureDetector(
child: Text(
'${widget.product.name}',
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
maxLines: 2,
softWrap: true,
style: new TextStyle(
fontSize: 15.0,
),
),
onTap: (){
_showProductDetail();
},
),
),
new Text(
widget.product.description,
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: new TextStyle(
fontSize: 12.0,
color: new Color(0xFF999999),
),
),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
child: widget.business.showMonthlySold ?
Text(
S.of(context).sold_per_month_token(widget.product.monthSales.toStringAsFixed(0)),
style: new TextStyle(
fontSize: 9.0
),
) : SizedBox.shrink(),
),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
ShowPrice(
widget.product.price,
regularPrice: widget.product.regularPrice,
currencySign: '\$',
),
],
),
],
),
new AddRemoveButton(product: widget.product, business: widget.business, addOnly: false,),
],
),
],
),
),
],
),
),
);
}
void _showProductDetail() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
ProductDetailPage(
product: widget.product, business: widget.business,)),
);
}
}

View File

@@ -0,0 +1,158 @@
import 'dart:async';
import 'package:fluro/fluro.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileRenewLicense extends StatefulWidget {
final int businessId;
const MobileRenewLicense(this.businessId, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileRenewLicenseState();
}
}
class MobileRenewLicenseState extends State<MobileRenewLicense> {
TextEditingController groupNumberController = TextEditingController();
bool canSubmit = false;
@override
Widget build(BuildContext context) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 10,),
child: Text(
S.of(context).renew_license,
style: TextStyle(
fontSize: 28,
),
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 0, left: 16, right: 16, bottom: 10),
child: Text(
S.of(context).group_number_can_be_found,
style: TextStyle(
color: Colors.black45,
),
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 0, bottom: 10),
child: Image.asset('assets/images/group_number.png',),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 0, bottom: 10),
child: Text(
S.of(context).group_number,
style: TextStyle(
fontSize: 28,
),
),
)
);
col.children.add(
Container(
padding: EdgeInsets.only(left: 16, right: 16, bottom: 0),
child: TextFormField(
controller: groupNumberController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).group_number,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).please_enter_group_number;
}
return null;
},
onChanged: (string) {
if (string.isNotEmpty) {
canSubmit = true;
} else {
canSubmit = false;
}
setState(() {});
},
),
)
);
col.children.add(Container(
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
child: ElevatedButton(
child: Text(
S.of(context).submit,
),
onPressed: canSubmit ? () {
_submit();
} : null,
),
));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).renew_license),
backgroundColor: Theme.of(context).primaryColor,
),
body: SingleChildScrollView(
child: col,
),
);
}
void _submit() {
HttpUtil.httpPost('v1/get-license-renewal',
(response) {
Routes.router.navigateTo(context, '/renew-minioffice/${response.data['id']}', replace: true);
},
body: {
'group_name': groupNumberController.text.trim(),
},
isFormData: true,
).onError((error, stackTrace) {
Utils.showMessageDialog(context, error);
});
}
}

View File

@@ -0,0 +1,159 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileRenewMiniOffice extends StatefulWidget {
final Map<String, dynamic> data;
const MobileRenewMiniOffice(this.data, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileRenewMiniOfficeState();
}
}
class MobileRenewMiniOfficeState extends State<MobileRenewMiniOffice> {
@override
Widget build(BuildContext context) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [Container(
padding: EdgeInsets.only(bottom: 16),
child: Text(
S.of(context).group_license_renewal,
style: TextStyle(
fontSize: 28,
),
),
)],
);
col.children.add(
buildLine(S.of(context).group_number, widget.data['group']['name'], valueSize: 18),
);
col.children.add(
buildLine(S.of(context).expiration_date,
Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['expiration_date']),
valueSize: 18
),
);
col.children.add(
buildLine(S.of(context).after_renewed,
Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['renewed_expiration_date']),
valueSize: 18
),
);
col.children.add(
buildLine(S.of(context).renewal_fee, '\$${widget.data['renewal_fee']}', valueSize: 18),
);
col.children.add(
buildLine(S.of(context).tax, '\$${widget.data['tax']}', valueSize: 18),
);
col.children.add(
buildLine(S.of(context).total, '\$${widget.data['renewal_total']}', valueSize: 38),
);
col.children.add(
Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(left: 0, right: 0, top: 20, bottom: 20),
child: ElevatedButton(
child: Container(
padding: EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
child: Text(
S.of(context).pay_amount_token(widget.data['renewal_total']),
style: TextStyle(
fontSize: 20,
),
),
),
onPressed: () {
_submit();
},
),
),
);
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).renew_license),
backgroundColor: Theme.of(context).primaryColor,
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
child: col,
),
),
);
}
Widget buildLine(String name, String value, {double nameSize, double valueSize}) {
Row row = Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
row.children.add(
Container(
width: 150,
padding: EdgeInsets.only(),
child: Text(
name,
style: TextStyle(
color: Colors.black38,
fontSize: nameSize,
),
),
),
);
row.children.add(
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Text(
value,
style: TextStyle(
fontSize: valueSize,
),
),
),
),
);
return Container(
padding: EdgeInsets.only(top: 8, bottom: 8),
child: row,
);
}
void _submit() {
if (store.state.user == null) {
Utils.requireLogin(context, returnUrl: '/renew-minioffice/${widget.data['group']['id']}');
return;
}
HttpUtil.httpPost('v1/create-minioffice-renewal-invoice',
(response) {
Routes.router.navigateTo(context, '/paynow/${response.data['order_id']}', replace: true);
},
body: widget.data,
).onError((error, stackTrace) {
Utils.showMessageDialog(context, error);
});
}
}

View File

@@ -0,0 +1,248 @@
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileResetPassword extends StatefulWidget {
final String mobile;
final String code;
const MobileResetPassword(this.mobile, {this.code, Key key})
: super(key: key);
@override
State<StatefulWidget> createState() {
return MobileResetPasswordState();
}
}
class MobileResetPasswordState extends State<MobileResetPassword> {
final GlobalKey<FormState> _formKey = GlobalKey();
final passwordController = TextEditingController();
final passwordAgainController = TextEditingController();
bool passwordVisible;
bool passwordAgainVisible;
bool canReset;
@override
void initState() {
super.initState();
canReset = false;
passwordVisible = true;
passwordAgainVisible = true;
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
S.of(context).reset_password,
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Container(
padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
S.of(context).reset_password_desc,
style: TextStyle(
fontSize: 15.0,
color: Colors.black54,
),
),
),
Container(
padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: TextFormField(
controller: passwordController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password,
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
return null;
},
obscureText: passwordVisible,
onChanged: (string) {
if (string.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: passwordAgainController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password_again,
suffixIcon: IconButton(
icon: Icon(
passwordAgainVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordAgainVisible = !passwordAgainVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
if (value.trim() != passwordController.text.trim()) {
return S.of(context).password_is_not_match_password_again;
}
return null;
},
obscureText: passwordAgainVisible,
onChanged: (string) {
if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
],
),
),
),
Container(
padding: EdgeInsets.only(right: 16.0, top: 16.0),
child: Align(
alignment: Alignment.centerRight,
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).submit,
style: TextStyle(
color: Colors.white,
),
),
onPressed: canReset ? resetPassword : null,
),
),
),
],
);
}
void resetPassword() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/users', (response) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).success),
content: Text(S.of(context).reset_password_success),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).ok),
onPressed: () {
Routes.router.navigateTo(context, '/login', replace: true, clearStack: false);
},
),
],
);
},
);
},
queryParameters: {
'action': 'reset_password',
},
isFormData: true,
body: {
'mobile': widget.mobile,
'code': widget.code,
'password': passwordController.text.trim()
}
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}
}

View File

@@ -0,0 +1,132 @@
import 'package:dio/dio.dart';
import 'package:flappy_search_bar/flappy_search_bar.dart';
import 'package:flutter/material.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/located_address.dart';
import '../../pages/new_address.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileSearchPlace extends StatefulWidget {
final Key key;
final int businessId;
const MobileSearchPlace(this.businessId, {this.key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileSearchPlaceState();
}
}
class MobileSearchPlaceState extends State<MobileSearchPlace> {
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
}
),
title: Text(
S.of(context).search_place,
),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: SearchBar(
minimumChars: 6,
hintText: S.of(context).enter_delivery_address,
cancellationWidget: Text(
S.of(context).cancel,
),
onSearch: search,
onItemFound: (LocatedAddress locatedAddress, int index) {
return ListTile(
title: Text(locatedAddress.streetName),
subtitle: Text(locatedAddress.formattedAddress),
onTap: () {
if (widget.businessId == 0) {
_selectPlace(locatedAddress);
} else {
_selectPlaceAsNew(locatedAddress);
}
},
dense: true,
);
},
onError: (error) {
return Center(
child: Text("$error"),
);
},
emptyWidget: Center(
child: widget.businessId > 0 ?
GestureDetector(
child: Text(
S.of(context).empty_address_change_keyword,
),
onTap: () {
_selectPlaceAsNew(null);
},
) :
Text(
S.of(context).empty_result_change_keyword
),
),
),
),
),
);
}
@override
void initState() {
super.initState();
}
Future<List<LocatedAddress>> search(String keyword) async {
var result = await HttpUtil.httpGet('v1/search-places',
queryParameters: {
'keyword': keyword,
},
returnError: true,
);
if (result is DioError) {
if (result.response != null) {
throw RuntimeError(result.response.data['message']);
} else {
throw RuntimeError(result.message);
}
} else if (result != null && result is List) {
return result.map((e) => LocatedAddress.fromJson(e)).toList();
}
return [];
}
void _selectPlace(LocatedAddress locatedAddress) {
store.dispatch(UpdateLocatedAddress(locatedAddress));
eventBus.fire(OnUpdateLocatedAddressSuccess());
Navigator.of(context).pop();
}
void _selectPlaceAsNew(LocatedAddress locatedAddress) {
print('located address: ${locatedAddress.toString()}');
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => NewAddress(locatedAddress: locatedAddress, businessId: widget.businessId,),
));
}
}

View File

@@ -0,0 +1,249 @@
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileSetPassword extends StatefulWidget {
final String mobile;
final String code;
const MobileSetPassword(this.mobile, {Key key, this.code}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileSetPasswordState();
}
}
class MobileSetPasswordState extends State<MobileSetPassword> {
final GlobalKey<FormState> _formKey = GlobalKey();
final passwordController = TextEditingController();
final passwordAgainController = TextEditingController();
bool passwordVisible;
bool passwordAgainVisible;
bool canReset;
@override
void initState() {
super.initState();
canReset = false;
passwordVisible = true;
passwordAgainVisible = true;
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
S.of(context).set_password,
style: TextStyle(
fontSize: 24.0,
color: Colors.black,
),
),
),
Container(
padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
S.of(context).set_password_desc,
style: TextStyle(
fontSize: 14.0,
color: Colors.black54,
),
),
),
Container(
padding: EdgeInsets.only(top: 0.0, left: 16.0, right: 16.0, bottom: 0.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: TextFormField(
controller: passwordController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password,
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
return null;
},
obscureText: passwordVisible,
onChanged: (string) {
if (string.trim().isNotEmpty && passwordAgainController.text.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
Container(
child: TextFormField(
controller: passwordAgainController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).password_again,
suffixIcon: IconButton(
icon: Icon(
passwordAgainVisible ? Icons.visibility_off : Icons.visibility,
color: Theme.of(context).primaryColorDark,
),
onPressed: () {
setState(() {
passwordAgainVisible = !passwordAgainVisible;
});
},
),
),
style: TextStyle(
fontSize: 18.0
),
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).password_is_required;
}
if (value.trim() != passwordController.text.trim()) {
return S.of(context).password_is_not_match_password_again;
}
return null;
},
obscureText: passwordAgainVisible,
onChanged: (string) {
if (passwordController.text.trim().isNotEmpty && string.trim().isNotEmpty) {
if (mounted) {
setState(() {
canReset = true;
});
}
} else {
if (mounted) {
setState(() {
canReset = false;
});
}
}
},
),
),
],
),
),
),
Container(
padding: EdgeInsets.only(right: 16.0, top: 16.0),
child: Align(
alignment: Alignment.centerRight,
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).submit,
style: TextStyle(
color: Colors.white,
),
),
onPressed: canReset ? resetPassword : null,
),
),
),
],
);
}
void resetPassword() {
final FormState form = _formKey.currentState;
if (form.validate()) {
HttpUtil.httpPost('v1/users', (response) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).success),
content: Text(S.of(context).user_account_created_success),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).ok),
onPressed: () {
Routes.router.navigateTo(context, '/login', replace: true, clearStack: false);
},
),
],
);
},
);
},
queryParameters: {
'action': 'create_user',
},
isFormData: true,
body: {
'mobile': widget.mobile,
'code': widget.code,
'password': passwordController.text.trim()
}
).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
import 'package:dio/dio.dart';
import 'package:flappy_search_bar/flappy_search_bar.dart';
import 'package:flutter/material.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/product.dart';
import '../../pages/product_detail_page.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../widgets/mobile/mobile_product_item.dart';
class MobileStoreProductSearch extends StatefulWidget {
final Key key;
final Business business;
const MobileStoreProductSearch(this.business, {this.key});
@override
State<StatefulWidget> createState() {
return MobileStoreProductSearchState();
}
}
class MobileStoreProductSearchState extends State<MobileStoreProductSearch> {
int page = 1;
int numPerPage = 10;
int lastResultSize = 0;
double lastBottomPosition = 0;
String lastKeyword = '';
SearchBarController _controller = SearchBarController<Product>();
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: NotificationListener<ScrollNotification>(
child: SearchBar(
searchBarController: _controller,
minimumChars: 2,
hintText: S.of(context).enter_product_keyword,
cancellationWidget: Text(
S.of(context).cancel,
),
onSearch: search,
onItemFound: (Product searchProduct, int index) {
return MobileProductItem(
product: searchProduct,
business: widget.business,
);
},
onError: (error) {
return Center(
child: Text("$error"),
);
},
emptyWidget: Center(
child: Text(
S.of(context).empty_result_change_keyword,
),
),
),
onNotification: (notification) {
if (notification.metrics.atEdge) {
print('fff: $page, $lastBottomPosition, ${notification.metrics.pixels}');
if (page != 0 && lastResultSize >= numPerPage && notification.metrics.pixels != 0) {
if (notification.metrics.pixels > lastBottomPosition) {
lastBottomPosition = notification.metrics.pixels;
page += 1;
_controller.replayLastSearch();
}
}
}
return true;
},
),
),
);
}
Future<List<Product>> search(String keyword) async {
if (lastKeyword != keyword) {
page = 1;
}
lastKeyword = keyword;
var result = await HttpUtil.httpGet('v1/search-store-product2',
queryParameters: {
'store_id': widget.business.id,
'keyword': keyword,
'page': page,
'num_per_page': numPerPage,
},
returnError: true,
);
if (result is DioError) {
if (result.response != null) {
throw RuntimeError(result.response.data);
} else {
throw RuntimeError(result.message);
}
} else if (result != null && result is List) {
lastBottomPosition = 0;
var r = result.map((e) => Product.fromJson(e)).toList();
lastResultSize = r.length;
if (lastResultSize < numPerPage) {
page = 1;
}
return r;
}
return [];
}
@override
void initState() {
super.initState();
eventBus.on<OnCartInfoUpdated>().listen((event) {
if (mounted) {
setState(() {});
}
});
}
void _showProductDetail(Product p) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
product: p,
business: widget.business,
)),
);
}
}

View File

@@ -0,0 +1,221 @@
import 'package:flutter/material.dart';
import 'package:stripe_sdk/stripe_sdk.dart';
import 'package:stripe_sdk/stripe_sdk_ui.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/order.dart';
import '../../models/payment_platform.dart';
import '../../models/stripe_payment_method.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/utils.dart';
class MobileStripePayWeb extends StatefulWidget {
final Key key;
final Order order;
final PaymentPlatform paymentPlatform;
final StripePaymentMethod stripePaymentMethod;
const MobileStripePayWeb(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod});
@override
State<StatefulWidget> createState() {
return MobileStripePayWebState();
}
}
class MobileStripePayWebState extends State<MobileStripePayWeb> {
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
final formKey = GlobalKey<FormState>();
final card = StripeCard();
CardForm form;
bool isSubmitting;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
Widget body = Center(
child: Icon(
Icons.credit_card,
size: 40.0,
color: Colors.black26,
),
);
if (widget.stripePaymentMethod == null) {
form = CardForm(card: card, formKey: formKey,);
body = ListView(
children: <Widget>[form,],
);
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (widget.stripePaymentMethod != null) {
_paymentWithPaymentMethod(context);
}
});
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).add_credit_card),
backgroundColor: Theme.of(context).primaryColor,
actions: <Widget>[
IconButton(
icon: Icon(Icons.check),
onPressed: widget.stripePaymentMethod == null ? () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
_paymentRequestWithCard(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
messageSnackBar(
context, S.of(context).this_credit_card_is_invalid
)
);
}
} : null,
),
],
),
body: body,
);
}
@override
void initState() {
super.initState();
isSubmitting = false;
StripeApi.init(widget.paymentPlatform.publishableKey);
}
SnackBar messageSnackBar(BuildContext context, String message) {
Column column = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[Text(
message,
style: TextStyle(
color: Colors.white,
),
)],
);
return SnackBar(
content: Container(
height: 45.0,
child: column,
),
action: SnackBarAction(
label: S.of(context).ok,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
);
}
_paymentWithPaymentMethod(BuildContext context) async {
Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId,
widget.stripePaymentMethod.paymentMethodId,
widget.stripePaymentMethod.paymentMethodType, (response) async {
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
await StripeApi.instance.confirmPaymentIntent(
response.data[Constants.STRIPE_CLIENT_SECRET],
data: {
'payment_method': response.data['payment_method'],
},
).then((result2) {
if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) {
Utils.stripeChargedSuccess(widget.order,
widget.stripePaymentMethod.paymentMethodId,
result2['id'], (response) {
if (isSubmitting) {
Navigator.of(context).pop();
}
eventBus.fire(OnOrderUpdated());
Routes.router.navigateTo(context, '/orderdetail/${widget
.order.id}', replace: true);
},
(showErrorDialog)
);
} else {
showErrorDialog(Exception('Unknown error'));
}
}).catchError(showErrorDialog);
}
}, (showErrorDialog));
isSubmitting = true;
Utils.showSubmitDialog(context);
}
_paymentRequestWithCard(BuildContext context) async {
isSubmitting = true;
Utils.showSubmitDialog(context);
await StripeApi.instance.createPaymentMethodFromCard(card)
.then((result) {
Utils.stripePaymentIntent(widget.order, null, result['id'], result['type'], (response) async {
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
await StripeApi.instance.confirmPaymentIntent(
response.data[Constants.STRIPE_CLIENT_SECRET],
data: {
'payment_method': response.data['payment_method'],
}
).then((result2) {
if (result2['status'] == Constants.STRIPE_STATUS_SUCCEDED) {
Utils.stripeChargedSuccess(widget.order,
result['id'], // payment method id
result2['id'], (response) { // payment intent id
if (isSubmitting) {
Navigator.of(context).pop();
}
eventBus.fire(OnOrderUpdated());
Routes.router.navigateTo(context, '/orderdetail/${widget
.order.id}', replace: true);
},
(showErrorDialog)
);
} else {
showErrorDialog(Exception('Unknown error'));
}
}).catchError(showErrorDialog);
}
}, (showErrorDialog),
cardBrand: result['card']['brand'],
cardCountry: result['card']['country'],
cardExpMonth: result['card']['exp_month'],
cardExpYear: result['card']['exp_year'],
cardFunding: result['card']['funding'],
cardLast4: result['card']['last4'],
);
}).catchError(showErrorDialog);
}
void showErrorDialog(dynamic error) {
if (isSubmitting) {
Navigator.of(context).pop();
isSubmitting = false;
}
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
}
}

View File

@@ -0,0 +1,92 @@
import '../../constants.dart';
import '../../utils/iframe_web.dart' if (dart.library.io) '../../utils/fake_iframe_web.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class MobileTutorials extends StatefulWidget {
const MobileTutorials({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileTutorialsState();
}
}
class MobileTutorialsState extends State<MobileTutorials> {
InAppWebViewController webView;
String url = "";
double progress = 0;
bool isLoadStop = false;
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: IFrameWeb(
width: double.maxFinite.toString(),
height: double.maxFinite.toString(),
src: Constants.TUTORIAL_URL,
),
),
],
);
} else {
print('progress: $progress');
return Stack(
children: [
InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse(Constants.TUTORIAL_URL)),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (controller, url) {
if (mounted) {
setState(() {
this.url = url.toString();
this.isLoadStop = false;
});
}
},
onLoadStop: (controller, url) async {
if (mounted) {
setState(() {
this.url = url.toString();
this.isLoadStop = true;
});
}
},
onProgressChanged: (InAppWebViewController controller, int progress) {
if (mounted) {
setState(() {
if (this.progress < 1.0) {
this.isLoadStop = false;
} else {
this.isLoadStop = true;
}
this.progress = progress / 100;
});
}
},
),
isLoadStop ? Container() :
Center(
child: CircularProgressIndicator(),
),
],
);
}
}
}

View File

@@ -0,0 +1,447 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/user.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
import '../../utils/utils.dart';
class MobileUserProfile extends StatefulWidget {
final Key key;
const MobileUserProfile({this.key})
: super(key: key);
@override
State<StatefulWidget> createState() {
return MobileUserProfileState();
}
}
class MobileUserProfileState extends State<MobileUserProfile> {
User _user;
bool _showProgress;
double _progress;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
return ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0, bottom: 10.0),
child: Text(
S.of(context).basic_info,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey
),
),
),
Stack(
children: <Widget>[
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
bottom: -85.0,
child: Visibility(
visible: _showProgress,
child: LinearPercentIndicator(
lineHeight: 10.0,
percent: _progress,
backgroundColor: Colors.transparent,
progressColor: Colors.blue,
linearStrokeCap: LinearStrokeCap.butt,
),
),
),
Positioned(
child: GestureDetector(
child: Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
S.of(context).avatar,
style: TextStyle(
),
),
),
Container(
child: Util.showImage('https:${_user.avatarUrl}',
width: 60,
height: 60,
fit: BoxFit.fill,
errorWidget: (context, url, error) => Icon(
Icons.account_circle,
size: 60.0,
color: Colors.grey,
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
onTap: () {
_getAvatar(context);
},
),
),
],
),
GestureDetector(
child: Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
S.of(context).nick_name,
style: TextStyle(
),
),
),
Container(
child: Text(
_user.nickname,
style: TextStyle(
color: Colors.grey
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
onTap: () {
_changeNickname(context);
},
),
GestureDetector(
child: Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Text(
S.of(context).my_addresses,
style: TextStyle(
),
),
),
Container(
child: Icon(
Icons.arrow_forward_ios,
size: 14.0,
color: Colors.grey,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
onTap: () {
Routes.router.navigateTo(context, '/my-addresses/-1');
},
),
Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 36.0, bottom: 10.0),
child: Text(
S.of(context).account_binding,
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
),
GestureDetector(
child: Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Icon(
Icons.phone_iphone,
size: 16.0,
),
),
Container(
margin: EdgeInsets.only(left: 5.0),
child: Text(
S.of(context).mobile_number,
style: TextStyle(
),
),
)
],
),
),
Container(
child: Text(
_user.mobile != null && _user.mobile.isNotEmpty ? Utils.safePhoneNumber(_user.mobile) : S.of(context).not_binding,
style: TextStyle(
color: Colors.grey,
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
onTap: () {
Routes.router.navigateTo(context, '/change-mobile-email/1');
},
),
GestureDetector(
child: Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: Icon(
Icons.mail_outline,
size: 16.0,
),
),
Container(
margin: EdgeInsets.only(left: 5.0),
child: Text(
S.of(context).email,
style: TextStyle(
),
),
)
],
),
),
Container(
child: Text(
_user.email != null && _user.email.isNotEmpty ? Utils.safePhoneNumber(_user.email) : S.of(context).not_binding,
style: TextStyle(
color: Colors.grey,
),
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.black12,
width: 1.0,
),
),
),
),
onTap: () {
Routes.router.navigateTo(context, '/change-mobile-email/0');
},
),
],
);
}
@override
void initState() {
super.initState();
if (store.state.user == null) {
Routes.router.navigateTo(context, '/login', replace: true);
} else {
print(store.state.user.toString());
setState(() {
_user = store.state.user;
_showProgress = false;
_progress = 0.0;
});
}
eventBus.on<OnCurrentUserUpdated>().listen((event) {
if (mounted) {
setState(() {
_user = store.state.user;
});
}
});
eventBus.on<OnProgressEvent>().listen((event) {
if (mounted) {
setState(() {
_showProgress = event.showProgress;
_progress = event.progress;
});
}
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
void _getAvatar(BuildContext mainContext) {
showDialog(
context: mainContext,
barrierDismissible: true,
builder: (BuildContext context) {
return Util().getPicture(mainContext, _user);
}
);
}
void _updateCurrentUser() {
store.dispatch(new UpdateCurrentUser(_user));
eventBus.fire(new OnCurrentUserUpdated());
}
void _changeNickname(BuildContext context) {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final nicknameController = TextEditingController();
nicknameController.text = _user.nickname;
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
S.of(context).change_nickname,
),
content: Container(
child: Form(
key: _formKey,
child: TextFormField(
controller: nicknameController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).enter_new_nickname,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).nickname_is_required;
}
return null;
},
),
),
),
actions: <Widget>[
FlatButton(
child: Text(
S.of(context).cancel
),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text(
S.of(context).submit_to_change,
),
onPressed: () {
final FormState form = _formKey.currentState;
if (form.validate()) {
Utils.getBox().then((box) {
int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0);
HttpUtil.httpPut('v1/users/$userId', (response) {
Navigator.of(context).pop();
if (mounted) {
setState(() {
_user = User.fromJson(response.data);
});
_updateCurrentUser();
}
},
body: {
'nickname': nicknameController.text
},
);
}).catchError((error){
Navigator.of(context).pop();
Routes.router.navigateTo(context, '/login');
});
}
},
)
],
);
}
);
}
}

View File

@@ -0,0 +1,151 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:photo_view/photo_view.dart';
import '../../constants.dart';
import '../../models/blog.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class MobileViewBlog extends StatefulWidget {
final Key key;
final int bid;
const MobileViewBlog(this.bid, {this.key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileViewBlogState();
}
}
class MobileViewBlogState extends State<MobileViewBlog> {
Blog blog;
@override
void initState() {
super.initState();
_loadBlog();
}
void _loadBlog() {
HttpUtil.httpGet(
'v1/blog/${widget.bid}',
businessId: Constants.BUSINESS_ID,
).then((value) {
if (mounted) {
setState(() {
blog = Blog.fromJson(value);
print('blog: $blog');
});
}
}).catchError((error) {
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.pop(context);
Routes.router.pop(context);
});
});
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
BuildContext mainContext = context;
if (blog == null) {
return Container(
padding: EdgeInsets.all(50.0),
child: Center(
child: SpinKitThreeBounce(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
Column view = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Text(
'${blog.title}',
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
),
Text(
Utils.utcDatetimeStringToLocalDatetimeString(context, blog.createdAt),
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
)
],
),
),
Container(
width: double.maxFinite,
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 24.0),
child: Text(
'${blog.body}',
style: TextStyle(
color: Colors.black87,
fontSize: 17.0,
),
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
Container(
width: double.maxFinite,
padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0, right: 16.0),
child: (blog.imageUrl != null) ?
Container(
width: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) - 100.0,
height: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) - 100.0,
child: PhotoView(
imageProvider: NetworkImage(
'https:${blog.imageUrl}',
),
),
) : SizedBox.shrink(),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black45,
),
),
),
),
],
);
return SingleChildScrollView(
child: view,
);
}
}

View File

@@ -0,0 +1,706 @@
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dash/flutter_dash.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_wisetronic/dialog/image_viewer.dart';
import 'package:flutter_wisetronic/events/eventbus.dart';
import 'package:flutter_wisetronic/events/events.dart';
import 'package:flutter_wisetronic/widgets/general/text_link.dart';
import 'package:percent_indicator/percent_indicator.dart';
import '../../constants.dart';
import '../../generated/l10n.dart';
import '../../models/ticket.dart';
import '../../models/user.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
class MobileViewTicket extends StatefulWidget {
final Key key;
final int ticketId;
const MobileViewTicket(this.ticketId, {this.key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileViewTicketState();
}
}
class MobileViewTicketState extends State<MobileViewTicket> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Ticket ticket;
final issueMsgController = TextEditingController();
bool onSubmitting = false;
double submitProgress = 0.0;
final List<Map<String, dynamic>> galleryImagesFlag = [
{'show': false, 'progress': 0.0, 'path': ''},
{'show': false, 'progress': 0.0, 'path': ''},
{'show': false, 'progress': 0.0, 'path': ''},
];
@override
void initState() {
super.initState();
onSubmitting = false;
_loadTicket();
}
void _loadTicket() {
HttpUtil.httpGet(
'v1/get-ticket/${widget.ticketId}',
businessId: Constants.BUSINESS_ID,
).then((value) {
if (mounted) {
setState(() {
ticket = Ticket.fromJson(value);
print('ticket: $ticket');
});
}
}).catchError((error) {
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.pop(context);
Routes.router.pop(context);
});
});
}
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
BuildContext mainContext = context;
if (ticket == null) {
return Container(
padding: EdgeInsets.all(50.0),
child: Center(
child: SpinKitThreeBounce(
color: Colors.lightBlueAccent,
size: 40.0,
),
),
);
}
Column view = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 16.0, top: 16.0, right: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
S.of(context).ticket_number_token(ticket.id),
style: TextStyle(
fontSize: 24.0,
color: Colors.black
),
),
Text(
Utils.utcDatetimeStringToLocalDatetimeString(context, ticket.createdAt),
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
)
],
),
),
Container(
width: double.maxFinite,
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 24.0),
child: Text(
'${ticket.issue.msg}',
style: TextStyle(
color: Colors.black54,
fontSize: 14.0,
),
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
Container(
width: double.maxFinite,
padding: EdgeInsets.only(top: 16.0, bottom: 16.0, left: 16.0, right: 16.0),
child: showGalleryImages(mainContext, ticket.issue.files),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black45,
),
),
),
),
Container(
width: double.maxFinite,
height: 10.0,
padding: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 8,
color: Colors.black26,
),
bottom: BorderSide(
width: 0.5,
color: Colors.white70,
),
),
),
),
],
);
if (ticket.followUps.length > 0) {
view.children.add(
Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Text(
S.of(context).follow_ups,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
);
for (int i = 0; i < ticket.followUps.length; i++) {
FollowUp followUp = ticket.followUps[i];
view.children.add(
Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${followUp.poster}',
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: Colors.black38,
),
),
Text(
Utils.utcDatetimeStringToLocalDatetimeString(
context,
followUp.createdAt
),
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
),
],
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
Container(
width: double.maxFinite,
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
child: Text(
'${followUp.msg}',
style: TextStyle(
color: Colors.black87,
),
),
),
Container(
padding: EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 20.0),
child: showGalleryImages(
mainContext, followUp.files,
),
),
],
),
),
);
}
}
Widget f = Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
S.of(context).your_reply,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
),
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
child: TextFormField(
controller: issueMsgController,
keyboardType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences,
decoration: new InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 1.0,
color: Colors.blue,
),
),
labelText: S.of(context).your_reply,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: false,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).this_field_is_required;
}
return null;
},
maxLines: 5,
maxLength: 1000,
),
),
Container(
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 4.0),
child: Text(
S.of(context).attach_pictures,
style: TextStyle(
fontSize: 17.0,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 16.0, bottom: 8.0),
child: Text(
S.of(context).attach_pictures_desc,
style: TextStyle(
fontSize: 14.0,
color: Colors.black38,
),
),
),
Container(
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0),
child: galleryImages(mainContext),
),
],
),
);
Widget s = Align(
alignment: Alignment.centerRight,
child: Container(
padding: EdgeInsets.only(top: 20.0, left: 16.0, right: 16.0, bottom: 20.0),
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text(
S.of(context).reply,
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {
_submit();
},
),
),
);
if (ticket.followUps.length > 0) {
view.children.add(
Container(
width: double.maxFinite,
height: 10.0,
padding: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 8,
color: Colors.black26,
),
bottom: BorderSide(
width: 0.5,
color: Colors.white70,
),
),
),
),
);
}
if (ticket.isClosed) {
view.children.add(
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0),
child: Text(
S.of(context).the_ticket_is_closed_desc,
style: TextStyle(
fontSize: 15.0,
color: Colors.black87,
),
),
),
);
view.children.add(
Container(
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 0.0, bottom: 30.0),
child: TextLink(
S.of(context).new_ticket,
'/new-ticket/${ticket.store.id}',
),
),
);
} else {
view.children.add(
f,
);
view.children.add(
s,
);
}
return SingleChildScrollView(
child: Stack(
children: [
Visibility(
visible: onSubmitting,
child: Container(
height: MediaQuery.of(context).size.height - MediaQuery.of(context).padding.bottom - MediaQuery.of(context).padding.top,
color: Colors.grey.withOpacity(0.6),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircularProgressIndicator(
strokeWidth: 10,
backgroundColor: Colors.green,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),
value: submitProgress,
),
Container(
margin: EdgeInsets.only(top: 16.0),
child: Text(
S.of(context).submitting_please_wait,
style: TextStyle(
color: Colors.white,
),
),
),
],
),
),
),
),
Visibility(
visible: !onSubmitting,
child: view,
),
],
),
);
}
Widget showGalleryImages(BuildContext mainContext, List<TicketFile> files) {
if (files.length <= 0) {
return SizedBox.shrink();
}
Row row = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[],
);
for (int i = 0; i < files.length; i++) {
TicketFile image = files[i];
Widget imageWidget;
imageWidget = GestureDetector(
child: Container(
margin: EdgeInsets.only(right: 20.0),
child: Util.showImage(
'https:${image.url}',
width: 80.0,
height: 80.0,
fit: BoxFit.fill
),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 0.5,
color: Colors.black12,
),
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
left: BorderSide(
width: 0.5,
color: Colors.black12,
),
right: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
onTap: () {
showDialog(
context: mainContext,
builder: (BuildContext context) {
return AlertDialog(
title: Text('${image.fileName}'),
content: ImageViewer(
NetworkImage(
'https:${image.url}',
),
),
actions: [
TextButton(
onPressed: () {
Routes.router.pop(context);
},
child: Text(S.of(context).close)
),
],
);
},
);
},
);
row.children.add(imageWidget);
}
return row;
}
Widget galleryImages(BuildContext mainContext) {
Row row = Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[],
);
for (int i = 0; i < 3; i++) {
Map<String, dynamic> image = galleryImagesFlag[i];
Widget imageWidget;
imageWidget = GestureDetector(
child: Stack(
alignment: Alignment.center,
children: [
Container(
margin: EdgeInsets.only(right: 20.0),
child: Util.showImage(
'${image['path']}',
width: 80.0,
height: 80.0,
fit: BoxFit.fill
),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 0.5,
color: Colors.black12,
),
bottom: BorderSide(
width: 0.5,
color: Colors.black12,
),
left: BorderSide(
width: 0.5,
color: Colors.black12,
),
right: BorderSide(
width: 0.5,
color: Colors.black12,
),
),
),
),
Visibility(
visible: galleryImagesFlag[i]['show'],
child: Container(
padding: EdgeInsets.only(right: 20.0),
child: CircularPercentIndicator(
radius: 60.0,
lineWidth: 10.0,
percent: galleryImagesFlag[i]['progress'],
center: new Text(
'${(galleryImagesFlag[i]['progress'] * 100.0).toStringAsFixed(1)}%',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15.0,
color: Colors.white,
),
),
circularStrokeCap: CircularStrokeCap.round,
progressColor: Colors.orange,
),
),
),
],
),
onTap: () {
showDialog(
context: mainContext,
builder: (BuildContext context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
return Util().getPicture2(mainContext, i, (int imageId, String path){
setState(() {
galleryImagesFlag[imageId]['path'] = path;
});
});
return null;
}
);
},
);
row.children.add(Badge(
position: BadgePosition.topEnd(top: -10.0, end: 10.0),
badgeContent: Container(
child: GestureDetector(
child: Icon(
Icons.clear,
color: Colors.white,
size: 14.0,
),
onTap: () {
_deleteImage(mainContext, i);
},
),
),
showBadge: image['path'].isNotEmpty ? true : false,
child: imageWidget,
));
}
return row;
}
void _deleteImage(BuildContext mainContext, int imageId) {
showDialog(context: mainContext,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).warning),
content: Text(S.of(context).are_you_sure_to_remove_the_picture),
actions: [
FlatButton(
child: Text(S.of(context).cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
RaisedButton(
child: Text(S.of(context).yes_i_am_sure),
color: Theme.of(context).primaryColor,
onPressed: () {
Navigator.of(context).pop();
setState(() {
galleryImagesFlag[imageId]['path'] = '';
});
},
),
],
);
},
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
eventBus.on<OnSubmitProgressEvent>().listen((event) {
if (mounted) {
setState(() {
onSubmitting = event.showProgress;
submitProgress = event.progress;
});
}
});
}
void _submit() {
final FormState form = _formKey.currentState;
if (form.validate()) {
setState(() {
onSubmitting = true;
});
Util().createTicket(context,
issueMsgController.text.trim(),
galleryImagesFlag,
(response){
showDialog(context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).success),
content: Text(S.of(context).ticket_created_success),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).ok),
onPressed: () {
Routes.router.navigateTo(context,
'/my-support/${ticket.store.id}',
replace: true,
);
},
),
],
);
});
}, (error) {
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.pop(context);
Routes.router.pop(context);
});
}, id: widget.ticketId);
}
}
}

View File

@@ -0,0 +1,249 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import '../../models/key_value.dart';
import 'package:image_picker/image_picker.dart';
import 'package:tesseract_ocr/tesseract_ocr.dart';
import '../../generated/l10n.dart';
class OCRScan extends StatefulWidget {
@override
State<StatefulWidget> createState() => OCRScanState();
}
class OCRScanState extends State<OCRScan> {
String _extractText;
List<KeyValue> docTypes = [];
KeyValue selectedDocType;
List<KeyValue> docLan = [];
KeyValue selectedDocLan;
bool _reading;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(S
.of(context)
.ocr_scan),
backgroundColor: Theme
.of(context)
.primaryColor,
),
body: ListView.builder(
itemCount: 4,
itemBuilder: (BuildContext context, int i) {
Widget item;
switch(i) {
case 0:
item = Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
child: Text(
S.of(context).document_type,
),
),
Container(
child: DropdownButton<KeyValue>(
items: docTypes.map((e) {
return DropdownMenuItem<KeyValue>(
value: e,
child: Text(
e.name
),
);
}).toList(),
hint: Text(
S.of(context).select_document_type,
),
onChanged: (newValue) {
setState(() {
selectedDocType = newValue;
});
},
isExpanded: true,
value: selectedDocType,
),
),
],
),
);
break;
case 1:
item = Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
child: Text(
S.of(context).document_langage,
),
),
Container(
child: DropdownButton<KeyValue>(
items: docLan.map((e) {
return DropdownMenuItem<KeyValue>(
value: e,
child: Text(
e.name
),
);
}).toList(),
hint: Text(
S.of(context).select_document_lanuage,
),
onChanged: (newValue) {
setState(() {
selectedDocLan = newValue;
});
},
isExpanded: true,
value: selectedDocLan,
),
),
],
),
);
break;
case 2:
item = Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(right: 10.0),
child: RaisedButton.icon(
elevation: 2.0,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(5.0),
),
color: const Color(0xFFFFB822),
icon: Icon(Icons.camera_alt_outlined),
label: Text(
S.of(context).from_camera,
),
onPressed: _reading != null && _reading == true ? null : () {
getPicFromCamera();
},
),
),
Container(
padding: EdgeInsets.only(right: 10.0),
child: RaisedButton.icon(
elevation: 2.0,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(5.0),
),
color: const Color(0xFFEFFE00),
icon: Icon(Icons.image_search),
label: Text(
S.of(context).from_gallery,
),
onPressed: _reading != null && _reading == true ? null : () {
getPicFromGallery();
},
),
),
],
),
);
break;
case 3:
item = SizedBox.shrink();
if (_reading != null && _reading) {
item = _reading == null ? SizedBox.shrink() :
Container(
padding: EdgeInsets.only(top: 12.0),
child: _reading ? SpinKitCircle(
size: 24.0,
color: Colors.blue,
) : Icon(Icons.done),
);
} else if (_extractText != null) {
item = Container(
child: RaisedButton.icon(
onPressed: () {
},
color: const Color(0xEE22EE00),
icon: Icon(Icons.arrow_circle_up),
label: Text(
S.of(context).submit_to_generate
)),
);
}
break;
}
return Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 8.0, bottom: 8.0),
child: item,
);
}
),
);
}
Future getPicFromCamera() async {
final picker = ImagePicker();
var image = await picker.getImage(source: ImageSource.camera);
print(image.path);
extractText(image.path);
}
Future getPicFromGallery() async {
final picker = ImagePicker();
var image = await picker.getImage(source: ImageSource.gallery);
print(image.path);
extractText(image.path);
}
Future extractText(String filePath) async {
int _extractTime = 0;
setState(() {
_reading = true;
_extractText = null;
});
var watch = Stopwatch()..start();
_extractText = await TesseractOcr.extractText(filePath, language: selectedDocLan.value);
_extractTime = watch.elapsedMilliseconds;
print(_extractText);
print('Time: $_extractTime');
setState(() {
_reading = false;
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
docTypes.add(KeyValue(S.of(context).ubereats_receipt, 'ubereats-receipt'));
docTypes.add(KeyValue(S.of(context).business_card, 'business-card'));
var eng = KeyValue(S.of(context).english, 'eng');
docLan.add(eng);
docLan.add(KeyValue(S.of(context).chinese_simplified, 'chi_sim'));
docLan.add(KeyValue(S.of(context).chinese_traditional, 'chi_tra'));
if (selectedDocLan == null) {
setState(() {
selectedDocLan = eng;
});
}
}
@override
void initState() {
super.initState();
}
}

View File

@@ -0,0 +1,326 @@
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/cart_info.dart';
import '../../models/cart_line_item.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/utils.dart';
import '../../widgets/general/add_remove_button.dart';
import '../../widgets/general/style.dart';
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
typedef OnEmptyCartListener();
typedef OnPanelOpenCloseRequest();
class ShoppingCartBar extends StatefulWidget {
final GlobalKey endKey;
final Business business;
final OnEmptyCartListener onEmptyCartListener;
final OnPanelOpenCloseRequest onPanelOpenCloseRequest;
final bool barAtBottom;
final bool hasPicture;
ShoppingCartBar({@required this.business, this.endKey,
this.onEmptyCartListener, this.onPanelOpenCloseRequest,
this.barAtBottom = false, this.hasPicture = false
});
@override
State<StatefulWidget> createState() {
return new ShoppingCartBarState();
}
}
class ShoppingCartBarState extends State<ShoppingCartBar> {
CartInfo cartInfo;
double totalPrice = 0.0;
@override
Widget build(BuildContext context) {
totalPrice = 0.0;
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
if (cartInfo != null && cartInfo.businessInfo.id == widget.business.id) {
totalPrice = cartInfo.getTotalPrice();
}
Widget cartContent;
if (cartInfo == null || (cartInfo.businessInfo.id != widget.business.id)
|| (totalPrice == 0.0 && cartInfo.productList.length == 0)) {
cartContent = Center(
child: Container(
padding: EdgeInsets.all(20.0),
child: Text(S.of(context).your_basket_is_empty),
),
);
} else {
cartContent = Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[],
);
(cartContent as Column).children.add(
GestureDetector(
child: Container(
padding: EdgeInsets.only(top: 12.0, left: 10.0, bottom: 12.0),
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(Icons.delete,
size: 14.0,
color: Colors.black38,
),
Text(
S.of(context).empty_basket,
style: TextStyle(
fontSize: 13.0,
color: Colors.black38
),
)
],
),
),
onTap: () {
Utils.clearCart(context, cartInfo, onComplete: (_) {
if (widget.onEmptyCartListener != null) {
widget.onEmptyCartListener();
}
});
},
),
);
for (var i = 0; i < cartInfo.productList.length; i++) {
(cartContent as Column).children.add(cartLineItem(cartInfo.productList[i], i));
}
}
Stack stack = Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
Container(
width: double.maxFinite,
height: 50.0,
child: Row(
children: <Widget>[
new Expanded(
flex: 2,
child: Container(
padding: new EdgeInsets.symmetric(vertical: 5).copyWith(left: 80.0),
color: new Color(0xFF3D3D3F),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Text(
'\$',
style: new TextStyle(
fontSize: 12.0,
color: Style.backgroundColor
),
),
new Text(
'${totalPrice.toStringAsFixed(2)}',
style: new TextStyle(
fontSize: 22.0,
color: Style.backgroundColor
),
),
],
),
new Text(
S.of(context).delivery_fee(widget.business.shippingFee.toStringAsFixed(2)),
style: new TextStyle(
fontSize: 9.0,
color: Style.backgroundColor,
),
),
],
),
)
),
new Expanded(
flex: 1,
child: GestureDetector(
child: Container(
padding: EdgeInsets.all(10.0),
color: widget.business.minPrice - totalPrice >= 0 ? new Color(0xFF535356) : Colors.lightGreen,
child: Center(
child: Text(
widget.business.minPrice - totalPrice >= 0 ? S.of(context).order_more((widget.business.minPrice - totalPrice).toStringAsFixed(2)) : S.of(context).checkout,
style: TextStyle(
fontSize: 14.0,
color: Style.backgroundColor,
),
),
),
),
onTap: widget.business.minPrice >= totalPrice ? null : () {
if (store.state.user != null) {
Routes.router.navigateTo(context, '/checkout/${widget.business.id}');
} else {
store.dispatch(UpdateRedirectRoute('/checkout/${widget.business.id}'));
Routes.router.navigateTo(context, '/login');
}
},
),
),
],
),
),
Positioned(
top: -10.0,
left: 20.0,
child: GestureDetector(
child: Container(
padding: new EdgeInsets.all(5.0),
decoration: new BoxDecoration(
color: new Color(0xFF3D3D3F),
shape: BoxShape.circle,
border: new Border.all(
color: new Color(0xFF3D3D3F),
width: 5.0,
)
),
child: Center(
child: Icon(
Icons.shopping_basket,
key: widget.endKey,
color: totalPrice > 0 ? Colors.orange : new Color(0xFF656565),
size: 36.0,
),
),
),
onTap: () {
if (widget.onPanelOpenCloseRequest != null) {
widget.onPanelOpenCloseRequest();
}
},
),
),
],
);
List<Widget> widgets = [];
if (widget.barAtBottom) {
widgets.add(Container(
width: double.maxFinite,
height: 200.0,
child: SingleChildScrollView(
child: cartContent,
),
));
widgets.add(stack);
} else {
widgets.add(stack);
widgets.add(Container(
width: double.maxFinite,
height: 200.0,
child: SingleChildScrollView(
child: cartContent,
),
));
}
return Material(
type: MaterialType.transparency,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: widgets,
),
);
}
Widget cartLineItem(CartLineItem item, int pidx) {
Widget lineItem = Container(
padding: EdgeInsets.only(top: 10.0),
height: 78.0,
decoration: BoxDecoration(
border: Border(
bottom: new BorderSide(
color: new Color(0xFFEBEBEB),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
widget.hasPicture ? Container(
padding: EdgeInsets.all(6.0),
child: Util.showImage(
'${item.product.imagePath}',
width: 80,
height: 80,
fit: BoxFit.cover,
),
) : SizedBox.shrink(),
Expanded(
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
item.name,
maxLines: 1,
style: TextStyle(
color: Colors.black87,
fontSize: 14.0
),
overflow: TextOverflow.ellipsis,
),
),
Container(
child: Text(
item.description,
style: TextStyle(
color: Colors.black38,
fontSize: 10.0
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
'${item.totalPrice.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.redAccent,
fontSize: 14.0,
),
),
),
Container(
padding: EdgeInsets.only(right: 10.0),
child: AddRemoveButton(
product: item.product,
business: widget.business,
cartLineItemIndex: pidx,
),
)
],
),
),
],
),
);
return lineItem;
}
}