backup. before shop update
This commit is contained in:
83
lib/widgets/mobile/MobileBottomNav.dart
Normal file
83
lib/widgets/mobile/MobileBottomNav.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
287
lib/widgets/mobile/mobile_attribute_selection.dart
Normal file
287
lib/widgets/mobile/mobile_attribute_selection.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
265
lib/widgets/mobile/mobile_blog.dart
Normal file
265
lib/widgets/mobile/mobile_blog.dart
Normal 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
170
lib/widgets/mobile/mobile_buy_service.dart
Normal file
170
lib/widgets/mobile/mobile_buy_service.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
357
lib/widgets/mobile/mobile_change_mobile_or_email.dart
Normal file
357
lib/widgets/mobile/mobile_change_mobile_or_email.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
286
lib/widgets/mobile/mobile_change_password.dart
Normal file
286
lib/widgets/mobile/mobile_change_password.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
2248
lib/widgets/mobile/mobile_checkout.dart
Normal file
2248
lib/widgets/mobile/mobile_checkout.dart
Normal file
File diff suppressed because it is too large
Load Diff
333
lib/widgets/mobile/mobile_contact_us.dart
Normal file
333
lib/widgets/mobile/mobile_contact_us.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
307
lib/widgets/mobile/mobile_coupons.dart
Normal file
307
lib/widgets/mobile/mobile_coupons.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
565
lib/widgets/mobile/mobile_edit_address.dart
Normal file
565
lib/widgets/mobile/mobile_edit_address.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
321
lib/widgets/mobile/mobile_forgot_password.dart
Normal file
321
lib/widgets/mobile/mobile_forgot_password.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
125
lib/widgets/mobile/mobile_igoshow_learn_more.dart
Normal file
125
lib/widgets/mobile/mobile_igoshow_learn_more.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class MobileIndexMainContent1State extends State<MobileIndexMainContent1> {
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen,
|
||||
color: Color(0xFF4FB0C6),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
275
lib/widgets/mobile/mobile_login.dart
Normal file
275
lib/widgets/mobile/mobile_login.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
842
lib/widgets/mobile/mobile_me.dart
Normal file
842
lib/widgets/mobile/mobile_me.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
92
lib/widgets/mobile/mobile_minipos_learn_more.dart
Normal file
92
lib/widgets/mobile/mobile_minipos_learn_more.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
227
lib/widgets/mobile/mobile_my_addresses.dart
Normal file
227
lib/widgets/mobile/mobile_my_addresses.dart
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
182
lib/widgets/mobile/mobile_my_cards.dart
Normal file
182
lib/widgets/mobile/mobile_my_cards.dart
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
295
lib/widgets/mobile/mobile_my_support.dart
Normal file
295
lib/widgets/mobile/mobile_my_support.dart
Normal 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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
395
lib/widgets/mobile/mobile_new_address.dart
Normal file
395
lib/widgets/mobile/mobile_new_address.dart
Normal 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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
372
lib/widgets/mobile/mobile_new_comment.dart
Normal file
372
lib/widgets/mobile/mobile_new_comment.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
400
lib/widgets/mobile/mobile_new_ticket.dart
Normal file
400
lib/widgets/mobile/mobile_new_ticket.dart
Normal 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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
319
lib/widgets/mobile/mobile_new_user.dart
Normal file
319
lib/widgets/mobile/mobile_new_user.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
1187
lib/widgets/mobile/mobile_order_detail.dart
Normal file
1187
lib/widgets/mobile/mobile_order_detail.dart
Normal file
File diff suppressed because it is too large
Load Diff
452
lib/widgets/mobile/mobile_orders.dart
Normal file
452
lib/widgets/mobile/mobile_orders.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
377
lib/widgets/mobile/mobile_pay_now.dart
Normal file
377
lib/widgets/mobile/mobile_pay_now.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
63
lib/widgets/mobile/mobile_plain_page.dart
Normal file
63
lib/widgets/mobile/mobile_plain_page.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
477
lib/widgets/mobile/mobile_product_detail_page.dart
Normal file
477
lib/widgets/mobile/mobile_product_detail_page.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
141
lib/widgets/mobile/mobile_product_item.dart
Normal file
141
lib/widgets/mobile/mobile_product_item.dart
Normal 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,)),
|
||||
);
|
||||
}
|
||||
}
|
||||
158
lib/widgets/mobile/mobile_renew_license.dart
Normal file
158
lib/widgets/mobile/mobile_renew_license.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
159
lib/widgets/mobile/mobile_renew_minioffice.dart
Normal file
159
lib/widgets/mobile/mobile_renew_minioffice.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
248
lib/widgets/mobile/mobile_reset_password.dart
Normal file
248
lib/widgets/mobile/mobile_reset_password.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
132
lib/widgets/mobile/mobile_search_place.dart
Normal file
132
lib/widgets/mobile/mobile_search_place.dart
Normal 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,),
|
||||
));
|
||||
}
|
||||
}
|
||||
249
lib/widgets/mobile/mobile_set_password.dart
Normal file
249
lib/widgets/mobile/mobile_set_password.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
2056
lib/widgets/mobile/mobile_shop.dart
Normal file
2056
lib/widgets/mobile/mobile_shop.dart
Normal file
File diff suppressed because it is too large
Load Diff
144
lib/widgets/mobile/mobile_store_product_search.dart
Normal file
144
lib/widgets/mobile/mobile_store_product_search.dart
Normal 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,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
221
lib/widgets/mobile/mobile_stripe_pay_web.dart
Normal file
221
lib/widgets/mobile/mobile_stripe_pay_web.dart
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
92
lib/widgets/mobile/mobile_tutorials.dart
Normal file
92
lib/widgets/mobile/mobile_tutorials.dart
Normal 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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
447
lib/widgets/mobile/mobile_user_profile.dart
Normal file
447
lib/widgets/mobile/mobile_user_profile.dart
Normal 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');
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
151
lib/widgets/mobile/mobile_view_blog.dart
Normal file
151
lib/widgets/mobile/mobile_view_blog.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
706
lib/widgets/mobile/mobile_view_ticket.dart
Normal file
706
lib/widgets/mobile/mobile_view_ticket.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
249
lib/widgets/mobile/ocr_scan.dart.unuse
Normal file
249
lib/widgets/mobile/ocr_scan.dart.unuse
Normal 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();
|
||||
}
|
||||
}
|
||||
326
lib/widgets/mobile/shopping_cart_bar.dart
Normal file
326
lib/widgets/mobile/shopping_cart_bar.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user