backup. before shop update
This commit is contained in:
143
lib/widgets/desktop/desktop_appbar_menu.dart
Normal file
143
lib/widgets/desktop/desktop_appbar_menu.dart
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../dialog/logout_dialog.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/user.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../widgets/general/navigationbar_logo.dart';
|
||||
import '../../widgets/general/text_link.dart';
|
||||
|
||||
class DesktopAppBarMenu extends StatelessWidget {
|
||||
User _user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_user = store.state.user;
|
||||
String currentRoute = ModalRoute.of(context).settings.name;
|
||||
Widget menuBar = Container(
|
||||
height: 56.0,
|
||||
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
|
||||
color: Colors.blue,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
NavigationBarLogo(),
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.only(left: 8.0, right: 16.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(width: 20.0,),
|
||||
TextLink(
|
||||
S.of(context).home,
|
||||
'/',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/',
|
||||
clearStack: true,
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).download,
|
||||
'/download',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/download',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).tutorials,
|
||||
Constants.TUTORIAL_URL,
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/tutorials',
|
||||
isLink: true,
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).support,
|
||||
'/my-support/${Constants.BUSINESS_ID}',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/my-support/${Constants.BUSINESS_ID}',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).shop,
|
||||
'/shop',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/shop',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).blog,
|
||||
'/blog/${Constants.BUSINESS_ID}',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/blog/${Constants.BUSINESS_ID}',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
_user != null ?
|
||||
(currentRoute == '/me') ?
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 4.0),
|
||||
child: Text(
|
||||
S.of(context).logout,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.logout,
|
||||
color: Colors.white,
|
||||
size: 16.0,
|
||||
)
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return logoutDialog(context);
|
||||
}
|
||||
);
|
||||
},
|
||||
),
|
||||
) : IconButton(
|
||||
icon: Icon(
|
||||
Icons.account_circle,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
Routes.router.navigateTo(context, '/me');
|
||||
},
|
||||
) :
|
||||
TextLink(
|
||||
S.of(context).login,
|
||||
'/login',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/login',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return menuBar;
|
||||
}
|
||||
|
||||
}
|
||||
314
lib/widgets/desktop/desktop_blog.dart
Normal file
314
lib/widgets/desktop/desktop_blog.dart
Normal file
@@ -0,0 +1,314 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.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/address.dart';
|
||||
import '../../models/blog.dart';
|
||||
import '../../models/ticket.dart';
|
||||
import '../../pages/edit_address.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../store/actions.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
|
||||
class DesktopBlog extends StatefulWidget {
|
||||
final int businessId;
|
||||
const DesktopBlog({Key key, this.businessId}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopBlogState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopBlogState extends State<DesktopBlog> {
|
||||
List<Blog> blogs;
|
||||
|
||||
double division = 2;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
BuildContext mainContext = context;
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).blog, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
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: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (blogs == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0,
|
||||
bottom: 16.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
),
|
||||
child: blogs == null ? Text('') :
|
||||
(blogs.length > 0 ?
|
||||
Wrap(
|
||||
children: blogs.map((a) => GestureDetector(
|
||||
child: _getBlog(a),
|
||||
onTap: () {
|
||||
Routes.router.navigateTo(context, '/view-blog/${a.id}');
|
||||
},
|
||||
)).toList(),
|
||||
) :
|
||||
Center(
|
||||
child: Text(S.of(context).no_blog_yet),
|
||||
)),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
child: row,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getBlog(Blog blog) {
|
||||
return Container(
|
||||
width: (mainSpace - 16.0) / division,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0,),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
top: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
left: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
right: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
(blog.thumbUrl != null) ?
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: Util.showImage(
|
||||
'https:${blog.thumbUrl}',
|
||||
width: 80,
|
||||
height: 80,
|
||||
),
|
||||
) : 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_refreshController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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_DESKTOP.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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
225
lib/widgets/desktop/desktop_buy_service.dart
Normal file
225
lib/widgets/desktop/desktop_buy_service.dart
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../constants.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';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopBuyService extends StatefulWidget {
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
const DesktopBuyService(this.data, {Key key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => DesktopBuyServiceState();
|
||||
}
|
||||
|
||||
class DesktopBuyServiceState extends State<DesktopBuyService> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
List<KeyValue> plans = [];
|
||||
KeyValue selectedPlan;
|
||||
double price = 0.0;
|
||||
double tax = 0.0;
|
||||
double paymentAmount = 0.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [],
|
||||
);
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
Column col2 = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 16, right: 16),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
|
||||
child: col1,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
width: 1,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
|
||||
child: col2,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).purchase_renew_service, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(child: row,),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
421
lib/widgets/desktop/desktop_change_mobile_or_email.dart
Normal file
421
lib/widgets/desktop/desktop_change_mobile_or_email.dart
Normal file
@@ -0,0 +1,421 @@
|
||||
|
||||
|
||||
import 'package:countdown/countdown.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.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 DesktopChangeMobileOrEmail extends StatefulWidget {
|
||||
final bool isMobile;
|
||||
|
||||
const DesktopChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopChangeMobileOrEmailState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopChangeMobileOrEmailState extends State<DesktopChangeMobileOrEmail> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
|
||||
final usernameController = TextEditingController();
|
||||
bool usernameEnable = true;
|
||||
final codeController = TextEditingController();
|
||||
|
||||
bool enableGetCode;
|
||||
String getCodeText;
|
||||
bool canRegister;
|
||||
|
||||
var countDownListener;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget view = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget v = SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
widget.isMobile ? S.of(context).change_mobile : S.of(context).change_email,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0,
|
||||
color: Colors.black
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 8.0, bottom: 16.0),
|
||||
child: Text(
|
||||
widget.isMobile ? S.of(context).change_mobile_desc : S.of(context).change_email_desc,
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: view,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
}
|
||||
348
lib/widgets/desktop/desktop_change_password.dart
Normal file
348
lib/widgets/desktop/desktop_change_password.dart
Normal file
@@ -0,0 +1,348 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
|
||||
class DesktopChangePassword extends StatefulWidget {
|
||||
const DesktopChangePassword({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopChangePasswordState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopChangePasswordState extends State<DesktopChangePassword> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
|
||||
final oldPasswordController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final passwordAgainController = TextEditingController();
|
||||
|
||||
bool passwordVisible;
|
||||
bool passwordAgainVisible;
|
||||
|
||||
bool canReset;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
canReset = false;
|
||||
passwordVisible = true;
|
||||
passwordAgainVisible = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
padding: EdgeInsets.only(top: 16.0, bottom: 20.0),
|
||||
child: Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
child: 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(left: 20.0, right: 20.0),
|
||||
child: Text(
|
||||
S.of(context).change_password_desc,
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
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),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
2268
lib/widgets/desktop/desktop_checkout.dart
Normal file
2268
lib/widgets/desktop/desktop_checkout.dart
Normal file
File diff suppressed because it is too large
Load Diff
357
lib/widgets/desktop/desktop_contact_us.dart
Normal file
357
lib/widgets/desktop/desktop_contact_us.dart
Normal file
@@ -0,0 +1,357 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/text_link.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:universal_io/io.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/business.dart';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
|
||||
class DesktopContactUs extends StatefulWidget {
|
||||
final Business business;
|
||||
|
||||
const DesktopContactUs(this.business, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopContactUsState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopContactUsState extends State<DesktopContactUs> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
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) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
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: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).contact_us, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(child: col,),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
330
lib/widgets/desktop/desktop_coupons.dart
Normal file
330
lib/widgets/desktop/desktop_coupons.dart
Normal file
@@ -0,0 +1,330 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
|
||||
import '../../constants.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';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopCoupons extends StatefulWidget {
|
||||
final int contactId;
|
||||
final Key key;
|
||||
const DesktopCoupons(this.contactId, {this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopCouponsState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopCouponsState extends State<DesktopCoupons> {
|
||||
List<Coupon> coupons;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget w = 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).coupons, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(child: w,),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/generated/l10n.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/download_item.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
@@ -34,8 +36,10 @@ class DesktopDownloadAppsState extends State<DesktopDownloadApps> {
|
||||
}
|
||||
Column col = Column(
|
||||
children: [
|
||||
Util.showImage(
|
||||
'https:${widget.data['download-image']['image']}'
|
||||
Container(
|
||||
child: Util.showImage(
|
||||
'https:${widget.data['download-image']['image']}'
|
||||
),
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@@ -50,20 +54,33 @@ class DesktopDownloadAppsState extends State<DesktopDownloadApps> {
|
||||
wrap.children.add(apps[i]);
|
||||
}
|
||||
col.children.add(wrap);
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: col,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: col,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
// return Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// BreadCrumbs(true, breadCrumbs: [
|
||||
// BreadCrumb(S.of(context).downloads, null)
|
||||
// ],),
|
||||
// Expanded(child: view),
|
||||
// ],
|
||||
// );
|
||||
return view;
|
||||
}
|
||||
|
||||
List<Widget> _getApps() {
|
||||
|
||||
589
lib/widgets/desktop/desktop_edit_address.dart
Normal file
589
lib/widgets/desktop/desktop_edit_address.dart
Normal file
@@ -0,0 +1,589 @@
|
||||
|
||||
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:gender_selection/gender_selection.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../events/eventbus.dart';
|
||||
import '../../events/events.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/address.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../store/actions.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopEditAddress extends StatefulWidget {
|
||||
final Key key;
|
||||
final Address address;
|
||||
final int businessId;
|
||||
const DesktopEditAddress(this.address, {this.key, int businessId}) :
|
||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopEditAddressState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopEditAddressState extends State<DesktopEditAddress> {
|
||||
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;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
if (showLoading) {
|
||||
return new Scaffold(
|
||||
body: Center(
|
||||
child: SpinKitWave(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget form = 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).edit_address,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).edit_address, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: form,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
380
lib/widgets/desktop/desktop_forgot_password.dart
Normal file
380
lib/widgets/desktop/desktop_forgot_password.dart
Normal file
@@ -0,0 +1,380 @@
|
||||
|
||||
import 'package:countdown/countdown.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.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 DesktopForgotPassword extends StatefulWidget {
|
||||
const DesktopForgotPassword({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopForgotPasswordState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopForgotPasswordState extends State<DesktopForgotPassword> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
|
||||
final usernameController = TextEditingController();
|
||||
bool usernameEnable = true;
|
||||
final codeController = TextEditingController();
|
||||
|
||||
bool enableGetCode;
|
||||
String getCodeText;
|
||||
bool canRegister;
|
||||
|
||||
var countDownListener;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget view = Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 0.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
S.of(context).forgot_password,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0,
|
||||
color: Colors.black
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 8.0, bottom: 16.0),
|
||||
child: Text(
|
||||
S.of(context).forgot_password_description,
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: view,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
}
|
||||
156
lib/widgets/desktop/desktop_igoshow_learn_more.dart
Normal file
156
lib/widgets/desktop/desktop_igoshow_learn_more.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
|
||||
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
|
||||
class DesktopiGoShowLearnMore extends StatefulWidget {
|
||||
final Map<String, dynamic> data;
|
||||
const DesktopiGoShowLearnMore(this.data, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopiGoShowLearnMoreState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopiGoShowLearnMoreState extends State<DesktopiGoShowLearnMore> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
Column col = Column(
|
||||
children: [
|
||||
Util.showImage(
|
||||
'https:${widget.data['image-top']['image']}'
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
);
|
||||
List<Widget> w = _getContent();
|
||||
Wrap wrap = Wrap(
|
||||
children: [],
|
||||
);
|
||||
for (int i = 0; i < w.length; i++) {
|
||||
wrap.children.add(w[i]);
|
||||
}
|
||||
col.children.add(wrap);
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: col,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
@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: [],
|
||||
);
|
||||
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: 1.7778,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
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(
|
||||
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(Container(
|
||||
width: mainSpace / 2,
|
||||
child: col,
|
||||
));
|
||||
}
|
||||
return widgets;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class DesktopIndexMainContent1State extends State<DesktopIndexMainContent1> {
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen,
|
||||
color: Color(0xFF4FB0C6),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/generated/l10n.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../widgets/general/text_link.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
|
||||
class DesktopIndexMainContent2 extends StatefulWidget {
|
||||
@@ -79,7 +80,7 @@ class DesktopIndexMainContent2State extends State<DesktopIndexMainContent2> {
|
||||
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
|
||||
),
|
||||
),
|
||||
@@ -126,7 +127,7 @@ class DesktopIndexMainContent2State extends State<DesktopIndexMainContent2> {
|
||||
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
|
||||
),
|
||||
),
|
||||
@@ -175,16 +176,12 @@ class DesktopIndexMainContent2State extends State<DesktopIndexMainContent2> {
|
||||
));
|
||||
}
|
||||
col.children.add(
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 0.0),
|
||||
child: Text(
|
||||
S.of(context).learn_more,
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextLink(
|
||||
S.of(context).learn_more,
|
||||
'/minipos-learn-more',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -225,16 +222,12 @@ class DesktopIndexMainContent2State extends State<DesktopIndexMainContent2> {
|
||||
));
|
||||
}
|
||||
col.children.add(
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 0.0),
|
||||
child: Text(
|
||||
S.of(context).learn_more,
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextLink(
|
||||
S.of(context).learn_more,
|
||||
'/igoshow-learn-more',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -34,148 +34,325 @@ class DesktopIndexMainContent3State extends State<DesktopIndexMainContent3> {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
margin: EdgeInsets.only(top: 20.0),
|
||||
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
|
||||
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 0.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xff262626),
|
||||
),
|
||||
child: Column(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
S.of(context).information,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
TextLink(S.of(context).service_policy, '/service_policy', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).return_policy, '/return_policy', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).privacy_policy, '/privacy_policy', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).license_agreement, '/license_agreement', paddingVertical: 5.0, paddingHorizontal: 10.0,)
|
||||
],
|
||||
),
|
||||
width: mainSpace,
|
||||
child: _getBody2(),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.0),
|
||||
child: Text(
|
||||
S.of(context).support,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).support_ticket, '/support_ticket', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).contact_us, '/contact_us', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).about_us, '/about_us', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).renew_license, '/renew_license', paddingVertical: 5.0, paddingHorizontal: 10.0,)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.0),
|
||||
child: Text(
|
||||
S.of(context).developer_of,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_GOOGLE,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_ALEXA,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_APPLE,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_EBAY,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_QUICKBOOKS,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_SHOPIFY,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Column _getBody() {
|
||||
Column col;
|
||||
col = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
S.of(context).information,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
TextLink(S.of(context).service_policy, '/service-policy', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).return_policy, '/return-policy', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).privacy_policy, '/privacy-policy', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).license_agreement, '/end-user-license-agreement', paddingVertical: 5.0, paddingHorizontal: 10.0,)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.0),
|
||||
child: Text(
|
||||
S.of(context).support,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).support_ticket, '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 10.0,),
|
||||
TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 10.0,)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.0),
|
||||
child: Text(
|
||||
S.of(context).developer_of,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_GOOGLE,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_ALEXA,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_APPLE,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_EBAY,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_QUICKBOOKS,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_SHOPIFY,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return col;
|
||||
}
|
||||
|
||||
Row _getBody2() {
|
||||
Row row;
|
||||
row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 10 * 3,
|
||||
padding: EdgeInsets.only(left: 16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
child: Text(
|
||||
S.of(context).information,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextLink(S.of(context).service_policy, '/service-policy', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).return_policy, '/return-policy', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).privacy_policy, '/privacy-policy', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).license_agreement, '/end-user-license-agreement', paddingVertical: 5.0, paddingHorizontal: 0.0,)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 10 * 3,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 0.0),
|
||||
child: Text(
|
||||
S.of(context).support,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextLink(S.of(context).wiki, '/wiki', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).support_ticket, '/my-support/${Constants.BUSINESS_ID}', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 0.0,),
|
||||
TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 0.0,)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 10 * 4,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 0.0),
|
||||
child: Text(
|
||||
S.of(context).developer_of,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_GOOGLE,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_ALEXA,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_APPLE,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_EBAY,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_QUICKBOOKS,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(
|
||||
IconData(
|
||||
Constants.FONT_SHOPIFY,
|
||||
fontFamily: 'wisetronic',
|
||||
fontPackage: null
|
||||
),
|
||||
color: Colors.white24,
|
||||
size: 48.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return row;
|
||||
}
|
||||
|
||||
}
|
||||
336
lib/widgets/desktop/desktop_login.dart
Normal file
336
lib/widgets/desktop/desktop_login.dart
Normal file
@@ -0,0 +1,336 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
|
||||
import '../../constants.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/utils.dart';
|
||||
|
||||
class DesktopLogin extends StatefulWidget {
|
||||
final Key key;
|
||||
|
||||
const DesktopLogin({this.key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopLoginState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopLoginState extends State<DesktopLogin> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
final usernameController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
bool passwordVisible;
|
||||
|
||||
bool onSubmitting;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
passwordVisible = true;
|
||||
onSubmitting = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
if (onSubmitting) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(50.0),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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: 12.0, left: 16.0, right: 16.0, bottom: 12.0),
|
||||
child: Text(
|
||||
S.of(context).login_instruction,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
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();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return 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) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
501
lib/widgets/desktop/desktop_me.dart
Normal file
501
lib/widgets/desktop/desktop_me.dart
Normal file
@@ -0,0 +1,501 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import 'package:fluttertoast/fluttertoast.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/util_web.dart'
|
||||
if (dart.library.io) '../../utils/util_io.dart';
|
||||
import '../../utils/utils.dart';
|
||||
|
||||
MediaQueryData mediaQuery;
|
||||
double statusBarHeight;
|
||||
double screenHeight;
|
||||
|
||||
class DesktopMe extends StatefulWidget {
|
||||
final Key key;
|
||||
|
||||
const DesktopMe({this.key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopMeState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopMeState extends State<DesktopMe> {
|
||||
int userId;
|
||||
String accessToken;
|
||||
|
||||
bool isLoading;
|
||||
User _user;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
double division = 3;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Widget userInfo = GestureDetector(
|
||||
child: Container(
|
||||
padding:
|
||||
EdgeInsets.only(left: 16.0, right: 16.0, top: 12.0, bottom: 5.0),
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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: Colors.blue,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
Icons.person_outline,
|
||||
size: 60.0,
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Text(
|
||||
_user != null ? _user.nickname : S.of(context).please_login,
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: _user != null,
|
||||
child: Container(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Icon(
|
||||
Icons.phone_iphone,
|
||||
color: Colors.blue,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Text(
|
||||
_user != null
|
||||
? Utils.safePhoneNumber(_user.mobile)
|
||||
: '',
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (_user != null) {
|
||||
Routes.router.navigateTo(context, '/user-profile');
|
||||
} else {
|
||||
Routes.router.navigateTo(context, '/login');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Widget widget = SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: userInfo,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
children: [
|
||||
getLinkItem(
|
||||
mainSpace / division,
|
||||
200.0,
|
||||
Icon(
|
||||
Icons.lock,
|
||||
size: 60.0,
|
||||
color: Colors.lightBlueAccent,
|
||||
),
|
||||
Text(S.of(context).change_password),
|
||||
() {
|
||||
if (_user != null) {
|
||||
Routes.router.navigateTo(
|
||||
context,
|
||||
'/change-password',
|
||||
);
|
||||
} else {
|
||||
_pleaseLoginToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
getLinkItem(
|
||||
mainSpace / division,
|
||||
200.0,
|
||||
Icon(
|
||||
Icons.sticky_note_2_outlined,
|
||||
size: 60.0,
|
||||
color: Colors.lightBlueAccent,
|
||||
),
|
||||
Text(S.of(context).my_orders),
|
||||
() {
|
||||
if (_user != null) {
|
||||
Routes.router.navigateTo(context, '/orders');
|
||||
} else {
|
||||
_pleaseLoginToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
getLinkItem(
|
||||
mainSpace / division,
|
||||
200.0,
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 60.0,
|
||||
color: Colors.lightBlueAccent,
|
||||
),
|
||||
Text(S.of(context).my_addresses),
|
||||
() {
|
||||
if (_user != null) {
|
||||
Routes.router
|
||||
.navigateTo(context, '/my-addresses/-1');
|
||||
} else {
|
||||
_pleaseLoginToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
getLinkItem(
|
||||
mainSpace / division,
|
||||
200.0,
|
||||
Icon(
|
||||
Icons.credit_card,
|
||||
size: 60.0,
|
||||
color: Colors.lightBlueAccent,
|
||||
),
|
||||
Text(S.of(context).my_cards),
|
||||
() {
|
||||
if (_user != null) {
|
||||
Routes.router.navigateTo(context, '/my-cards');
|
||||
} else {
|
||||
_pleaseLoginToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
getLinkItem(
|
||||
mainSpace / division,
|
||||
200.0,
|
||||
Icon(
|
||||
Icons.support_agent,
|
||||
size: 60.0,
|
||||
color: Colors.lightBlueAccent,
|
||||
),
|
||||
Text(S.of(context).my_support),
|
||||
() {
|
||||
if (_user != null) {
|
||||
Routes.router.navigateTo(context,
|
||||
'/my-support/${Constants.BUSINESS_ID}');
|
||||
} else {
|
||||
_pleaseLoginToast();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
_user = null;
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Widget getLinkItem(
|
||||
double width, double height, Icon icon, Text label, Function tap) {
|
||||
Widget widget = MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
||||
child: Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
child: icon,
|
||||
),
|
||||
Container(
|
||||
child: label,
|
||||
),
|
||||
],
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.rectangle,
|
||||
border: new Border.all(
|
||||
color: Colors.black12,
|
||||
width: 1.0,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: tap,
|
||||
),
|
||||
);
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
124
lib/widgets/desktop/desktop_minipos_learn_more.dart
Normal file
124
lib/widgets/desktop/desktop_minipos_learn_more.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
|
||||
class DesktopMiniPosLearnMore extends StatefulWidget {
|
||||
final Map<String, dynamic> data;
|
||||
const DesktopMiniPosLearnMore(this.data, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopMiniPosLearnMoreState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopMiniPosLearnMoreState extends State<DesktopMiniPosLearnMore> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
Column col = Column(
|
||||
children: [
|
||||
Util.showImage(
|
||||
'https:${widget.data['image-top']['image']}'
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
);
|
||||
List<Widget> w = _getContent();
|
||||
Wrap wrap = Wrap(
|
||||
children: [],
|
||||
);
|
||||
for (int i = 0; i < w.length; i++) {
|
||||
wrap.children.add(w[i]);
|
||||
}
|
||||
col.children.add(wrap);
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: col,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
@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(
|
||||
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: 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(
|
||||
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(Container(
|
||||
width: mainSpace / 2,
|
||||
child: col,
|
||||
));
|
||||
}
|
||||
return widgets;
|
||||
}
|
||||
}
|
||||
269
lib/widgets/desktop/desktop_my_addresses.dart
Normal file
269
lib/widgets/desktop/desktop_my_addresses.dart
Normal file
@@ -0,0 +1,269 @@
|
||||
|
||||
|
||||
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/address.dart';
|
||||
import '../../pages/edit_address.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../store/actions.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopMyAddresses extends StatefulWidget {
|
||||
final int businessId;
|
||||
const DesktopMyAddresses({Key key, this.businessId}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopMyAddressesState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopMyAddressesState extends State<DesktopMyAddresses> {
|
||||
List<Address> addresses;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
double division = 2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
if (addresses == null) {
|
||||
return new Scaffold(
|
||||
body: Center(
|
||||
child: SpinKitWave(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BuildContext mainContext = context;
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).my_addresses,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).my_addresses, null),
|
||||
BreadCrumb(S.of(context).add_new_address,
|
||||
'/search-place/${widget.businessId}',
|
||||
),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0,
|
||||
bottom: 16.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
),
|
||||
child: addresses == null ? Text('') :
|
||||
(addresses.length > 0 ?
|
||||
Wrap(
|
||||
children: addresses.map((a) => _getAddress(a)).toList(),
|
||||
) :
|
||||
Center(
|
||||
child: Text(S.of(context).no_address_yet),
|
||||
)),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getAddress(Address address) {
|
||||
|
||||
return Container(
|
||||
width: (mainSpace - 16.0) / division,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0,),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
top: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
left: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
right: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
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,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
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(context, error, onOk: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
},
|
||||
) : SizedBox.shrink(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
208
lib/widgets/desktop/desktop_my_cards.dart
Normal file
208
lib/widgets/desktop/desktop_my_cards.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../constants.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';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopMyCards extends StatefulWidget {
|
||||
final Key key;
|
||||
const DesktopMyCards({this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return MyCardsState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyCardsState extends State<DesktopMyCards> {
|
||||
User _user;
|
||||
|
||||
bool isSubmitting;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).my_cards, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _user.stripePaymentMethods.length,
|
||||
itemBuilder: (BuildContext context, int position) {
|
||||
StripePaymentMethod paymentMethod = _user.stripePaymentMethods[position];
|
||||
return cardWidget(paymentMethod);
|
||||
}
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
343
lib/widgets/desktop/desktop_my_support.dart
Normal file
343
lib/widgets/desktop/desktop_my_support.dart
Normal file
@@ -0,0 +1,343 @@
|
||||
|
||||
|
||||
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/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/double_back_to_close_app_wrapper.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopMySupport extends StatefulWidget {
|
||||
final int businessId;
|
||||
const DesktopMySupport({Key key, this.businessId}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopMySupportState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopMySupportState extends State<DesktopMySupport> {
|
||||
List<Ticket> tickets;
|
||||
|
||||
double division = 3;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
if (store.state.user == null) {
|
||||
Utils.requireLogin(context, returnUrl: '/my-support/${widget.businessId}');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
BuildContext mainContext = context;
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).my_support,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).my_support, null),
|
||||
BreadCrumb(S.of(context).add_new_ticket,
|
||||
'/new-ticket/${widget.businessId}',
|
||||
icon: Icons.add,
|
||||
),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
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: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (tickets == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0,
|
||||
bottom: 16.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
),
|
||||
child: tickets == null ? Text('') :
|
||||
(tickets.length > 0 ?
|
||||
Wrap(
|
||||
children: tickets.map((a) => _getTicket(a)).toList(),
|
||||
) :
|
||||
Center(
|
||||
child: Text(S.of(context).no_ticket_yet),
|
||||
)),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
child: row,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_refreshController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _getTicket(Ticket ticket) {
|
||||
return Container(
|
||||
width: (mainSpace - 16.0) / division,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 16.0, right: 16.0,),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
top: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
left: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
right: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 0.6,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
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(),
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.of(context).followups_token(ticket.followUps.length),
|
||||
style: TextStyle(
|
||||
fontSize: 15.0,
|
||||
color: Colors.black87,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () {
|
||||
Routes.router.navigateTo(context, '/view-ticket/${ticket.id}');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
eventBus.on<OnTicketsUpdated>().listen((event) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
tickets = null;
|
||||
});
|
||||
}
|
||||
_refreshController.requestRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
void loadTicketes(bool isRefresh) {
|
||||
HttpUtil.httpGet(
|
||||
'v1/mysupport',
|
||||
businessId: widget.businessId,
|
||||
queryParameters: {
|
||||
'page': _page.toString(),
|
||||
'size': Constants.TICKET_PER_PAGE_DESKTOP.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,12 +1,29 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/generated/l10n.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/navigationbar_logo.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/text_link.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_wisetronic/widgets/desktop/desktop_appbar_menu.dart';
|
||||
import '../../widgets/general/breadcrumbs.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/store.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/navigationbar_logo.dart';
|
||||
import '../../widgets/general/text_link.dart';
|
||||
|
||||
class DesktopNavigationBar extends StatefulWidget {
|
||||
const DesktopNavigationBar({Key key}) : super(key: key);
|
||||
final bool hasBack;
|
||||
final List<BreadCrumb> breadCrumbs;
|
||||
final Widget shoppingCart;
|
||||
|
||||
const DesktopNavigationBar({Key key, this.hasBack, this.breadCrumbs,
|
||||
this.shoppingCart}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -16,92 +33,201 @@ class DesktopNavigationBar extends StatefulWidget {
|
||||
}
|
||||
|
||||
class DesktopNavigationBarState extends State<DesktopNavigationBar> {
|
||||
User _user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String currentRoute = ModalRoute.of(context).settings.name;
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.blue,
|
||||
height: 80.0,
|
||||
),
|
||||
Container(
|
||||
height: 80.0,
|
||||
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
NavigationBarLogo(),
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.only(left: 8.0, right: 16.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(width: 20.0,),
|
||||
TextLink(
|
||||
S.of(context).home,
|
||||
'/',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/',
|
||||
clearStack: true,
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).download,
|
||||
'/download',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/download',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).tutorials,
|
||||
'/tutorials',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/tutorials',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).support,
|
||||
'/support',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/support',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).shop,
|
||||
'/shop',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/shop',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).blog,
|
||||
'/blog',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/blog',
|
||||
),
|
||||
SizedBox(width: 15.0,),
|
||||
TextLink(
|
||||
S.of(context).login,
|
||||
'/login',
|
||||
color: Colors.white,
|
||||
selected: currentRoute == '/login',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// String currentRoute = ModalRoute.of(context).settings.name;
|
||||
// Stack stack = Stack(
|
||||
// children: [
|
||||
// Container(
|
||||
// color: Colors.blue,
|
||||
// height: 80.0,
|
||||
// ),
|
||||
// Container(
|
||||
// height: 80.0,
|
||||
// padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// NavigationBarLogo(),
|
||||
// Expanded(
|
||||
// child: Container(
|
||||
// alignment: Alignment.centerRight,
|
||||
// padding: EdgeInsets.only(left: 8.0, right: 16.0),
|
||||
// child: SingleChildScrollView(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// SizedBox(width: 20.0,),
|
||||
// TextLink(
|
||||
// S.of(context).home,
|
||||
// '/',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/',
|
||||
// clearStack: true,
|
||||
// ),
|
||||
// SizedBox(width: 15.0,),
|
||||
// TextLink(
|
||||
// S.of(context).download,
|
||||
// '/download',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/download',
|
||||
// ),
|
||||
// SizedBox(width: 15.0,),
|
||||
// TextLink(
|
||||
// S.of(context).tutorials,
|
||||
// '/tutorials',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/tutorials',
|
||||
// ),
|
||||
// SizedBox(width: 15.0,),
|
||||
// TextLink(
|
||||
// S.of(context).support,
|
||||
// '/my-support/${Constants.BUSINESS_ID}',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/my-support/${Constants.BUSINESS_ID}',
|
||||
// ),
|
||||
// SizedBox(width: 15.0,),
|
||||
// TextLink(
|
||||
// S.of(context).shop,
|
||||
// '/shop',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/shop',
|
||||
// ),
|
||||
// SizedBox(width: 15.0,),
|
||||
// TextLink(
|
||||
// S.of(context).blog,
|
||||
// '/blog/${Constants.BUSINESS_ID}',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/blog/${Constants.BUSINESS_ID}',
|
||||
// ),
|
||||
// SizedBox(width: 15.0,),
|
||||
// _user != null ?
|
||||
// (currentRoute == '/me') ?
|
||||
// MouseRegion(
|
||||
// cursor: SystemMouseCursors.click,
|
||||
// child: GestureDetector(
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Container(
|
||||
// padding: EdgeInsets.only(right: 4.0),
|
||||
// child: Text(
|
||||
// S.of(context).logout,
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Icon(
|
||||
// Icons.logout,
|
||||
// color: Colors.white,
|
||||
// size: 16.0,
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// onTap: () {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (BuildContext context) {
|
||||
// return logoutDialog(context);
|
||||
// }
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ) : IconButton(
|
||||
// icon: Icon(
|
||||
// Icons.account_circle,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Routes.router.navigateTo(context, '/me');
|
||||
// },
|
||||
// ) :
|
||||
// TextLink(
|
||||
// S.of(context).login,
|
||||
// '/login',
|
||||
// color: Colors.white,
|
||||
// selected: currentRoute == '/login',
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
Widget sc = SizedBox.shrink();
|
||||
if (widget.shoppingCart != null) {
|
||||
sc = widget.shoppingCart;
|
||||
}
|
||||
Widget breadCrumbBar = SizedBox.shrink();
|
||||
if (widget.breadCrumbs != null && widget.breadCrumbs.length > 0) {
|
||||
breadCrumbBar = Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 38.0,
|
||||
color: Color(0xFF4FB0C6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BreadCrumbs(
|
||||
widget.hasBack ?? false,
|
||||
breadCrumbs: widget.breadCrumbs,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
sc,
|
||||
Container(
|
||||
width: 1.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
// stack.children.add(
|
||||
// Positioned(
|
||||
// top: 90.0,
|
||||
// child: breadCrumbBar,
|
||||
// ),
|
||||
// );
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DesktopAppBarMenu(),
|
||||
breadCrumbBar,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@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) {
|
||||
Utils.getCurrentUser();
|
||||
});
|
||||
}
|
||||
}
|
||||
424
lib/widgets/desktop/desktop_new_address.dart
Normal file
424
lib/widgets/desktop/desktop_new_address.dart
Normal file
@@ -0,0 +1,424 @@
|
||||
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import 'package:gender_selection/gender_selection.dart';
|
||||
|
||||
import '../../constants.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';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopNewAddress extends StatefulWidget {
|
||||
final Key key;
|
||||
final LocatedAddress locatedAddress;
|
||||
final int businessId;
|
||||
const DesktopNewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopNewAddressState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopNewAddressState extends State<DesktopNewAddress> {
|
||||
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;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
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));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget form = 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).new_address,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).new_address, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: form,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
406
lib/widgets/desktop/desktop_new_comment.dart
Normal file
406
lib/widgets/desktop/desktop_new_comment.dart
Normal file
@@ -0,0 +1,406 @@
|
||||
|
||||
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/navigationbar.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
import 'package:smooth_star_rating/smooth_star_rating.dart';
|
||||
|
||||
import '../../constants.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 DesktopNewComment extends StatefulWidget {
|
||||
final Key key;
|
||||
final int orderId;
|
||||
const DesktopNewComment(this.orderId, {this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopNewCommentState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopNewCommentState extends State<DesktopNewComment> {
|
||||
Comment comment;
|
||||
|
||||
bool _showProgress;
|
||||
|
||||
double _progress;
|
||||
|
||||
double rating;
|
||||
|
||||
bool isSubmitting = false;
|
||||
|
||||
final TextEditingController commentController = new TextEditingController();
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).new_comment,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).pay_now, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
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) {
|
||||
|
||||
ListView listView = ListView.builder(
|
||||
itemCount: 4,
|
||||
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;
|
||||
case 3:
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: SizedBox(
|
||||
width: 150.0,
|
||||
height: 36.0,
|
||||
child: RaisedButton(
|
||||
child: Text(
|
||||
S.of(context).submit,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
color: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
_submit();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
return Row(
|
||||
children: [
|
||||
Container(width: sideSpace,),
|
||||
Expanded(child: listView,),
|
||||
Container(width: sideSpace,),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
459
lib/widgets/desktop/desktop_new_ticket.dart
Normal file
459
lib/widgets/desktop/desktop_new_ticket.dart
Normal file
@@ -0,0 +1,459 @@
|
||||
|
||||
import 'package:badges/badges.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 '../../constants.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/utils.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../utils/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
|
||||
class DesktopNewTicket extends StatefulWidget {
|
||||
final Key key;
|
||||
final int businessId;
|
||||
|
||||
const DesktopNewTicket({this.key, int businessId}) :
|
||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopNewTicketState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopNewTicketState extends State<DesktopNewTicket> {
|
||||
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': ''},
|
||||
];
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
onSubmitting = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
BuildContext mainContext = context;
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Row row = Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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: 12.0, left: 16.0, right: 16.0, bottom: 12.0),
|
||||
child: Text(
|
||||
S.of(context).add_new_ticket_desc,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
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: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
height: 120.0,
|
||||
padding: EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
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).login,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
_submit();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: onSubmitting,
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height - 100.0,
|
||||
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: row,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
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/${widget.businessId}', replace: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}, (error) {
|
||||
Utils.showMessageDialog(context, error, onOk: () {
|
||||
Routes.router.pop(context);
|
||||
Routes.router.pop(context);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
379
lib/widgets/desktop/desktop_new_user.dart
Normal file
379
lib/widgets/desktop/desktop_new_user.dart
Normal file
@@ -0,0 +1,379 @@
|
||||
|
||||
import 'package:countdown/countdown.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.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 DesktopNewUser extends StatefulWidget {
|
||||
const DesktopNewUser({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopNewUserState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopNewUserState extends State<DesktopNewUser> {
|
||||
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
|
||||
final usernameController = TextEditingController();
|
||||
bool usernameEnable = true;
|
||||
final codeController = TextEditingController();
|
||||
|
||||
bool enableGetCode;
|
||||
String getCodeText;
|
||||
bool canRegister;
|
||||
|
||||
var countDownListener;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget view = Column(
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: view,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
}
|
||||
1219
lib/widgets/desktop/desktop_order_detail.dart
Normal file
1219
lib/widgets/desktop/desktop_order_detail.dart
Normal file
File diff suppressed because it is too large
Load Diff
467
lib/widgets/desktop/desktop_orders.dart
Normal file
467
lib/widgets/desktop/desktop_orders.dart
Normal file
@@ -0,0 +1,467 @@
|
||||
|
||||
|
||||
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 '../../pages/order_detail.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../store/actions.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/double_back_to_close_app.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/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
|
||||
class DesktopOrders extends StatefulWidget {
|
||||
final Key key;
|
||||
const DesktopOrders({this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopOrdersState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopOrdersState extends State<DesktopOrders> with SingleTickerProviderStateMixin {
|
||||
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
List<Order> orders;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
int _page = 1;
|
||||
int _pageCount = 1;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
ScrollController scrollController = ScrollController();
|
||||
|
||||
|
||||
@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;
|
||||
}
|
||||
});
|
||||
|
||||
if (orders == null) {
|
||||
return new Scaffold(
|
||||
body: Center(
|
||||
child: SpinKitWave(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget body = NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
if (!_isLoading && scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
|
||||
if (_pageCount > _page) {
|
||||
setState(() {
|
||||
_page += 1;
|
||||
_isLoading = true;
|
||||
});
|
||||
_loadData();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: _buildBody(),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).my_orders, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: DoubleBackToCloseApp(
|
||||
snackBar: SnackBar(
|
||||
content: Text(S.of(context).tap_back_again_to_exit),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(
|
||||
child: body,
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
|
||||
return ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: orders != null && orders.length > 0 ? orders.length + 1 : 1,
|
||||
itemBuilder: (BuildContext context, int position) {
|
||||
Order order;
|
||||
if (orders != null && orders.length > 0 && position < orders.length) {
|
||||
order = orders[position];
|
||||
}
|
||||
|
||||
if (order == null && orders.length <= 0) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).you_have_no_orders_yet,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (position >= orders.length) {
|
||||
if (_pageCount > _page) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).no_more_record,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
_loadData() {
|
||||
HttpUtil.httpGet('v1/orders',
|
||||
queryParameters: {
|
||||
'expand': 'cart_info',
|
||||
'page': _page.toString(),
|
||||
'size': Constants.ORDERS_PER_PAGE.toString(),
|
||||
},
|
||||
).then((data) {
|
||||
// Utils.jsonPrettyPrint(data);
|
||||
_page = int.parse(data['_meta']['currentPage'].toString());
|
||||
_pageCount = int.parse(data['_meta']['pageCount'].toString());
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
if (orders == null) {
|
||||
orders = [];
|
||||
}
|
||||
orders.addAll((data['items'] as List).map((e) => Order.fromJson(e)).toList());
|
||||
});
|
||||
}
|
||||
}).catchError((error) {
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
396
lib/widgets/desktop/desktop_pay_now.dart
Normal file
396
lib/widgets/desktop/desktop_pay_now.dart
Normal file
@@ -0,0 +1,396 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/navigationbar.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 DesktopPayNow extends StatefulWidget {
|
||||
final Key key;
|
||||
final int orderId;
|
||||
const DesktopPayNow(this.orderId, {this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopPayNowState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopPayNowState extends State<DesktopPayNow> {
|
||||
Order order;
|
||||
List<PaymentPlatform> paymentPlatforms;
|
||||
User _user;
|
||||
|
||||
bool nativePay;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
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: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).pay_now, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Container(width: sideSpace,),
|
||||
Expanded(child: listView,),
|
||||
Container(width: sideSpace,),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 198.54.117.10
|
||||
88
lib/widgets/desktop/desktop_plain_page.dart
Normal file
88
lib/widgets/desktop/desktop_plain_page.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/bottom_nav.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/blog.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopPlainPage extends StatefulWidget {
|
||||
final Blog blog;
|
||||
|
||||
const DesktopPlainPage(this.blog, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopPlainPageState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopPlainPageState extends State<DesktopPlainPage> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
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: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(widget.blog.title, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(child: col,),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
479
lib/widgets/desktop/desktop_product_detail_page.dart
Normal file
479
lib/widgets/desktop/desktop_product_detail_page.dart
Normal file
@@ -0,0 +1,479 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.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/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/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/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/carousel.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
import '../../widgets/general/show_price.dart';
|
||||
import 'desktop_shopping_cart_widget.dart';
|
||||
|
||||
class DesktopProductDetailPage extends StatefulWidget {
|
||||
final Business business;
|
||||
final Product product;
|
||||
|
||||
const DesktopProductDetailPage(
|
||||
{@required this.business, @required this.product, Key key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopProductDetailPageState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopProductDetailPageState extends State<DesktopProductDetailPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
|
||||
TabController _tabController;
|
||||
|
||||
final double _tabBarHeight = 50;
|
||||
|
||||
ProductDetail productDetail;
|
||||
|
||||
bool refresh;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@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;
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).shop,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(productDetail.name, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
// shoppingCart: DesktopShoppingCartWidget(business: widget.business,),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 30.0, left: 16.0, right: 16.0, bottom: 30.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
getImage(mainSpace * 0.50),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 10),
|
||||
child: Text(
|
||||
'SKU: ${productDetail.sku}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Text(
|
||||
productDetail.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 10, bottom: 20),
|
||||
child: Text(
|
||||
'${productDetail.description}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace * 0.5,
|
||||
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ShowPrice(
|
||||
productDetail.price,
|
||||
currencySign: '\$',
|
||||
fontWeight: FontWeight.bold,
|
||||
smallFontSize: 24,
|
||||
largeFontSize: 40,
|
||||
regularPrice: productDetail.regularPrice,
|
||||
),
|
||||
AddRemoveButton(
|
||||
product: widget.product,
|
||||
business: widget.business,
|
||||
addToBasket: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: mainSpace / 2,
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: <Widget>[
|
||||
SingleChildScrollView(
|
||||
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, bottom: 16.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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getImage(double width) {
|
||||
if (productDetail.images.length <= 0) {
|
||||
return Util.showImage(
|
||||
productDetail.image,
|
||||
width: width,
|
||||
height: width,
|
||||
);
|
||||
} else {
|
||||
return Carousel(
|
||||
height: width,
|
||||
pages: _getProductPictures(context),
|
||||
autoPlay: true,
|
||||
duration: Duration(seconds: 10,),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget subProducts(List<Subproduct> subproducts) {
|
||||
Column col = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 12, 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,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
setState(() {
|
||||
refresh = false;
|
||||
});
|
||||
super.initState();
|
||||
_tabController = TabController(vsync: this, length: 2);
|
||||
HttpUtil.httpGet(
|
||||
'v1/product-detail/${widget.product.id}',
|
||||
businessId: widget.business.id,
|
||||
).then((value) {
|
||||
print(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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
230
lib/widgets/desktop/desktop_product_item.dart
Normal file
230
lib/widgets/desktop/desktop_product_item.dart
Normal file
@@ -0,0 +1,230 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/show_price.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/style.dart';
|
||||
import 'desktop_product_detail_page.dart';
|
||||
|
||||
class DesktopProductItem extends StatefulWidget {
|
||||
final Product product;
|
||||
final Business business;
|
||||
final bool horizontal;
|
||||
|
||||
DesktopProductItem({this.product, this.business, Key key, this.horizontal = false})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopProductItemState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopProductItemState extends State<DesktopProductItem> {
|
||||
int _qty = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
width: 160.0,
|
||||
height: widget.horizontal ? 160 : 268.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: Container(
|
||||
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0),
|
||||
child: widget.horizontal ? getHorizontal() : getVertial(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getHorizontal() {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
margin: new EdgeInsets.all(10.0),
|
||||
width: 110.0,
|
||||
height: 110.0,
|
||||
child: GestureDetector(
|
||||
child: Util.showImage('${widget.product.imagePath}',
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
onTap: (){
|
||||
_showProductDetail();
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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();
|
||||
},
|
||||
),
|
||||
),
|
||||
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(),
|
||||
),
|
||||
ShowPrice(
|
||||
widget.product.price,
|
||||
regularPrice: widget.product.regularPrice,
|
||||
currencySign: '\$',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
],
|
||||
),
|
||||
new AddRemoveButton(product: widget.product, business: widget.business, addOnly: false,),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget getVertial() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
margin: new EdgeInsets.all(10.0),
|
||||
width: 110.0,
|
||||
height: 110.0,
|
||||
child: GestureDetector(
|
||||
child: Util.showImage('${widget.product.imagePath}',
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
onTap: (){
|
||||
_showProductDetail();
|
||||
},
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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();
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: 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(),
|
||||
),
|
||||
ShowPrice(
|
||||
widget.product.price,
|
||||
regularPrice: widget.product.regularPrice,
|
||||
currencySign: '\$',
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
209
lib/widgets/desktop/desktop_renew_license.dart
Normal file
209
lib/widgets/desktop/desktop_renew_license.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopRenewLicense extends StatefulWidget {
|
||||
final int businessId;
|
||||
|
||||
const DesktopRenewLicense(this.businessId, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopRenewLicenseState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopRenewLicenseState extends State<DesktopRenewLicense> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
TextEditingController groupNumberController = TextEditingController();
|
||||
|
||||
bool canSubmit = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [],
|
||||
);
|
||||
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 20, bottom: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
S.of(context).renew_license,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 4, bottom: 4),
|
||||
child: Text(
|
||||
S.of(context).group_number_can_be_found,
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 4),
|
||||
child: Image.asset(
|
||||
'assets/images/group_number.png',
|
||||
width: 400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
width: 1,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 20, bottom: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(bottom: 4,),
|
||||
child: Text(
|
||||
S.of(context).group_number,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 4,),
|
||||
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(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: ElevatedButton(
|
||||
child: Text(
|
||||
S.of(context).submit,
|
||||
),
|
||||
onPressed: canSubmit ? () {
|
||||
_submit();
|
||||
} : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).renew_license, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(child: row,),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
179
lib/widgets/desktop/desktop_renew_minioffice.dart
Normal file
179
lib/widgets/desktop/desktop_renew_minioffice.dart
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../routes.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/bottom_nav.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
class DesktopRenewMiniOffice extends StatefulWidget {
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
const DesktopRenewMiniOffice(this.data, {Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopRenewMiniOfficeState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopRenewMiniOfficeState extends State<DesktopRenewMiniOffice> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [],
|
||||
);
|
||||
|
||||
Column col1 = 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
col1.children.add(
|
||||
Utils.buildLine(S.of(context).group_number, widget.data['group']['name'], valueSize: 18),
|
||||
);
|
||||
col1.children.add(
|
||||
Utils.buildLine(S.of(context).expiration_date,
|
||||
Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['expiration_date']),
|
||||
valueSize: 18
|
||||
),
|
||||
);
|
||||
col1.children.add(
|
||||
Utils.buildLine(S.of(context).after_renewed,
|
||||
Utils.utcDatetimeStringToLocalDatetimeString(context, widget.data['renewed_expiration_date']),
|
||||
valueSize: 18
|
||||
),
|
||||
);
|
||||
col1.children.add(
|
||||
Utils.buildLine(S.of(context).renewal_fee, '\$${widget.data['renewal_fee']}', valueSize: 18),
|
||||
);
|
||||
col1.children.add(
|
||||
Utils.buildLine(S.of(context).tax, '\$${widget.data['tax']}', valueSize: 18),
|
||||
);
|
||||
col1.children.add(
|
||||
Utils.buildLine(S.of(context).total, '\$${widget.data['renewal_total']}', valueSize: 38),
|
||||
);
|
||||
|
||||
Column col2 = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 16, right: 16),
|
||||
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();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
|
||||
child: col1,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
width: 1,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 20),
|
||||
child: col2,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).renew_license, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(child: row,),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomNav(),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
304
lib/widgets/desktop/desktop_reset_password.dart
Normal file
304
lib/widgets/desktop/desktop_reset_password.dart
Normal file
@@ -0,0 +1,304 @@
|
||||
|
||||
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 DesktopResetPassword extends StatefulWidget {
|
||||
final String mobile;
|
||||
final String code;
|
||||
|
||||
const DesktopResetPassword(this.mobile, {this.code, Key key}) :
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() =>
|
||||
DesktopResetPasswordState();
|
||||
}
|
||||
|
||||
class DesktopResetPasswordState extends State<DesktopResetPassword> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
|
||||
final passwordController = TextEditingController();
|
||||
final passwordAgainController = TextEditingController();
|
||||
|
||||
bool passwordVisible;
|
||||
bool passwordAgainVisible;
|
||||
|
||||
bool canReset;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
canReset = false;
|
||||
passwordVisible = true;
|
||||
passwordAgainVisible = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget form = 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: 0.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 16.0, top: 0.0, right: 16.0),
|
||||
child: Text(
|
||||
S.of(context).reset_password,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0,
|
||||
color: Colors.black
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 12.0, left: 16.0, right: 16.0, bottom: 12.0),
|
||||
child: Text(
|
||||
S.of(context).reset_password_desc,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: form,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
153
lib/widgets/desktop/desktop_search_place.dart
Normal file
153
lib/widgets/desktop/desktop_search_place.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flappy_search_bar/flappy_search_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/breadcrumbs.dart';
|
||||
import 'package:flutter_wisetronic/widgets/general/navigationbar.dart';
|
||||
|
||||
import '../../constants.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 DesktopSearchPlace extends StatefulWidget {
|
||||
final Key key;
|
||||
final int businessId;
|
||||
const DesktopSearchPlace(this.businessId, {this.key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopSearchPlaceState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopSearchPlaceState extends State<DesktopSearchPlace> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).search_place,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).search_place, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 16.0, right: 16.0, bottom: 16.0, left: 16.0),
|
||||
width: mainSpace,
|
||||
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
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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,),
|
||||
));
|
||||
}
|
||||
}
|
||||
304
lib/widgets/desktop/desktop_set_password.dart
Normal file
304
lib/widgets/desktop/desktop_set_password.dart
Normal file
@@ -0,0 +1,304 @@
|
||||
|
||||
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 DesktopSetPassword extends StatefulWidget {
|
||||
final String mobile;
|
||||
final String code;
|
||||
|
||||
const DesktopSetPassword(this.mobile, {this.code, Key key}) :
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() =>
|
||||
DesktopSetPasswordState();
|
||||
}
|
||||
|
||||
class DesktopSetPasswordState extends State<DesktopSetPassword> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
|
||||
final passwordController = TextEditingController();
|
||||
final passwordAgainController = TextEditingController();
|
||||
|
||||
bool passwordVisible;
|
||||
bool passwordAgainVisible;
|
||||
|
||||
bool canReset;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
canReset = false;
|
||||
passwordVisible = true;
|
||||
passwordAgainVisible = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget form = 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 16.0, top: 0.0, right: 16.0),
|
||||
child: Text(
|
||||
S.of(context).set_password,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0,
|
||||
color: Colors.black
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 12.0, left: 16.0, right: 16.0, bottom: 12.0),
|
||||
child: Text(
|
||||
S.of(context).set_password_desc,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: form,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
2218
lib/widgets/desktop/desktop_shop.dart
Normal file
2218
lib/widgets/desktop/desktop_shop.dart
Normal file
File diff suppressed because it is too large
Load Diff
67
lib/widgets/desktop/desktop_shopping_cart_widget.dart
Normal file
67
lib/widgets/desktop/desktop_shopping_cart_widget.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../../models/business.dart';
|
||||
import '../../models/cart_info.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/utils.dart';
|
||||
|
||||
class DesktopShoppingCartWidget extends StatefulWidget {
|
||||
final Business business;
|
||||
final Function onTap;
|
||||
|
||||
DesktopShoppingCartWidget({@required this.business, this.onTap});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => DesktopShoppingCartWidgetState();
|
||||
}
|
||||
|
||||
class DesktopShoppingCartWidgetState extends State<DesktopShoppingCartWidget> {
|
||||
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();
|
||||
}
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 4.0, top: 8.0),
|
||||
child: Icon(
|
||||
Icons.shopping_basket_outlined,
|
||||
size: 24,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 4.0, right: 16.0, top: 8.0),
|
||||
child: Text(
|
||||
'\$${totalPrice.toStringAsFixed(2)}',
|
||||
style: TextStyle(
|
||||
color: totalPrice > 0 ? Colors.red : Colors.black45,
|
||||
fontWeight: totalPrice > 0 ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
child: Container(
|
||||
child: row,
|
||||
width: 160.0,
|
||||
),
|
||||
onTap: widget.onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
173
lib/widgets/desktop/desktop_store_product_search.dart
Normal file
173
lib/widgets/desktop/desktop_store_product_search.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flappy_search_bar/flappy_search_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_wisetronic/pages/product_detail_page.dart';
|
||||
import 'package:flutter_wisetronic/widgets/desktop/desktop_product_item.dart';
|
||||
|
||||
import '../../events/eventbus.dart';
|
||||
import '../../events/events.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/business.dart';
|
||||
import '../../models/product.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/add_remove_button.dart';
|
||||
|
||||
class DesktopStoreProductSearch extends StatefulWidget {
|
||||
final Key key;
|
||||
final Business business;
|
||||
|
||||
const DesktopStoreProductSearch(this.business, {this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopStoreProductSearchState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopStoreProductSearchState extends State<DesktopStoreProductSearch> {
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
double rate = 1;
|
||||
|
||||
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));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
rate = mainSpace / 1200;
|
||||
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
padding: EdgeInsets.all(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 DesktopProductItem(
|
||||
product: searchProduct,
|
||||
business: widget.business,
|
||||
horizontal: true,
|
||||
);
|
||||
},
|
||||
onError: (error) {
|
||||
return Center(
|
||||
child: Text("$error"),
|
||||
);
|
||||
},
|
||||
emptyWidget: Center(
|
||||
child: Text(
|
||||
S.of(context).empty_result_change_keyword,
|
||||
),
|
||||
),
|
||||
),
|
||||
onNotification: (notification) {
|
||||
if (notification.metrics.atEdge) {
|
||||
if (page != 0 && lastResultSize >= numPerPage && notification.metrics.pixels != 0) {
|
||||
if (notification.metrics.pixels > lastBottomPosition) {
|
||||
lastBottomPosition = notification.metrics.pixels;
|
||||
page += 1;
|
||||
_controller.replayLastSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
250
lib/widgets/desktop/desktop_stripe_pay_web.dart
Normal file
250
lib/widgets/desktop/desktop_stripe_pay_web.dart
Normal file
@@ -0,0 +1,250 @@
|
||||
|
||||
|
||||
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';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
import '../../widgets/general/navigationbar.dart';
|
||||
|
||||
|
||||
class DesktopStripePayWeb extends StatefulWidget {
|
||||
final Key key;
|
||||
final Order order;
|
||||
final PaymentPlatform paymentPlatform;
|
||||
final StripePaymentMethod stripePaymentMethod;
|
||||
const DesktopStripePayWeb(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopStripePayWebState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopStripePayWebState extends State<DesktopStripePayWeb> {
|
||||
|
||||
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final card = StripeCard();
|
||||
|
||||
CardForm form;
|
||||
|
||||
bool isSubmitting;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
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,
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 20.0, bottom: 20.0, right: 16.0),
|
||||
alignment: Alignment.centerRight,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).submit,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (formKey.currentState.validate()) {
|
||||
formKey.currentState.save();
|
||||
_paymentRequestWithCard(context);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messageSnackBar(
|
||||
context, S.of(context).this_credit_card_is_invalid
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
if (widget.stripePaymentMethod != null) {
|
||||
_paymentWithPaymentMethod(context);
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: NavigationBar(
|
||||
title: S.of(context).blog,
|
||||
back: true,
|
||||
breadCrumbs: [
|
||||
BreadCrumb(S.of(context).add_credit_card, null),
|
||||
],
|
||||
breadCrumbHeight: Constants.BREADCRUMB_HEIGHT,
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Container(width: sideSpace,),
|
||||
Expanded(child: body,),
|
||||
Container(width: sideSpace,),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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();
|
||||
});
|
||||
}
|
||||
}
|
||||
127
lib/widgets/desktop/desktop_tutorials.dart
Normal file
127
lib/widgets/desktop/desktop_tutorials.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
|
||||
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 DesktopTutorials extends StatefulWidget {
|
||||
const DesktopTutorials({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopTutorialsState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DesktopTutorialsState extends State<DesktopTutorials> {
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
InAppWebViewController webView;
|
||||
String url = "";
|
||||
double progress = 0;
|
||||
bool isLoadStop = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
if (kIsWeb) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IFrameWeb(
|
||||
width: double.maxFinite.toString(),
|
||||
height: double.maxFinite.toString(),
|
||||
src: Constants.TUTORIAL_URL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
print('progress: $progress');
|
||||
return Stack(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Expanded(
|
||||
child: 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;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
isLoadStop ? Container() :
|
||||
Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
480
lib/widgets/desktop/desktop_user_profile.dart
Normal file
480
lib/widgets/desktop/desktop_user_profile.dart
Normal file
@@ -0,0 +1,480 @@
|
||||
|
||||
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';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
|
||||
class DesktopUserProfile extends StatefulWidget {
|
||||
final Key key;
|
||||
|
||||
const DesktopUserProfile({this.key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopUserProfileState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopUserProfileState extends State<DesktopUserProfile> {
|
||||
User _user;
|
||||
|
||||
bool _showProgress;
|
||||
double _progress;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 20.0, top: 20.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
@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');
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
194
lib/widgets/desktop/desktop_view_blog.dart
Normal file
194
lib/widgets/desktop/desktop_view_blog.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
||||
import '../../constants.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/utils.dart';
|
||||
import '../../widgets/general/breadcrumbs.dart';
|
||||
|
||||
class DesktopViewBlog extends StatefulWidget {
|
||||
final Key key;
|
||||
final int bid;
|
||||
|
||||
const DesktopViewBlog(this.bid, {this.key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopViewBlogState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopViewBlogState extends State<DesktopViewBlog> {
|
||||
Blog blog;
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@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));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
if (blog == null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(50.0),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BuildContext mainContext = context;
|
||||
|
||||
Column blogCol = 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: Container(
|
||||
child: Text(
|
||||
'${blog.body}',
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 17.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 0.5,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: blogCol,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: (blog.imageUrl == null) ?
|
||||
SizedBox.shrink()
|
||||
: Container(
|
||||
width: mainSpace / 2 - 100.0,
|
||||
height: mainSpace / 2 - 100.0,
|
||||
child: PhotoView(
|
||||
imageProvider: NetworkImage(
|
||||
'https:${blog.imageUrl}',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
708
lib/widgets/desktop/desktop_view_ticket.dart
Normal file
708
lib/widgets/desktop/desktop_view_ticket.dart
Normal file
@@ -0,0 +1,708 @@
|
||||
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:percent_indicator/percent_indicator.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../dialog/image_viewer.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/util_web.dart' if (dart.library.io) '../../utils/util_io.dart';
|
||||
import '../../utils/utils.dart';
|
||||
import '../../widgets/general/text_link.dart';
|
||||
|
||||
class DesktopViewTicket extends StatefulWidget {
|
||||
final Key key;
|
||||
final int ticketId;
|
||||
|
||||
const DesktopViewTicket(this.ticketId, {this.key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DesktopViewTicketState();
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopViewTicketState extends State<DesktopViewTicket> {
|
||||
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': ''},
|
||||
];
|
||||
|
||||
double sideSpace = 0;
|
||||
double mainSpace = 1200;
|
||||
|
||||
@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));
|
||||
|
||||
if (MediaQuery.of(context).size.width <= 1200) {
|
||||
mainSpace = MediaQuery.of(context).size.width;
|
||||
sideSpace = 0;
|
||||
} else {
|
||||
mainSpace = 1200;
|
||||
sideSpace = (MediaQuery.of(context).size.width - 1200) / 2;
|
||||
}
|
||||
|
||||
if (ticket == null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(50.0),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Colors.lightBlueAccent,
|
||||
size: 40.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BuildContext mainContext = context;
|
||||
|
||||
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: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Container(
|
||||
height: 100.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();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Column ticketCol = 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: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
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) {
|
||||
ticketCol.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];
|
||||
ticketCol.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: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: showGalleryImages(
|
||||
mainContext, followUp.files,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget view = SingleChildScrollView(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
Container(
|
||||
width: mainSpace,
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: ticketCol,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
width: 1.0,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: mainSpace / 2,
|
||||
margin: EdgeInsets.only(top: 16.0, bottom: 16.0),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: (ticket.isClosed) ?
|
||||
Column(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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}',
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: <Widget>[
|
||||
f,
|
||||
s,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: sideSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return 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 Container(
|
||||
height: 100.0,
|
||||
child: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
395
lib/widgets/general/add_remove_button.dart
Normal file
395
lib/widgets/general/add_remove_button.dart
Normal file
@@ -0,0 +1,395 @@
|
||||
|
||||
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import '../../events/eventbus.dart';
|
||||
import '../../events/events.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/business.dart';
|
||||
import '../../models/cart_info.dart';
|
||||
import '../../models/product.dart';
|
||||
import '../../pages/attribute_selection.dart';
|
||||
import '../../store/actions.dart';
|
||||
import '../../store/store.dart';
|
||||
import '../../utils/utils.dart';
|
||||
|
||||
class AddRemoveButton extends StatefulWidget {
|
||||
final Product product;
|
||||
final Business business;
|
||||
final bool addOnly;
|
||||
final int cartLineItemIndex;
|
||||
final bool addToBasket;
|
||||
|
||||
AddRemoveButton({
|
||||
this.product,
|
||||
this.business,
|
||||
this.addOnly = false,
|
||||
this.cartLineItemIndex = -1,
|
||||
this.addToBasket = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new AddRemoveButtonState();
|
||||
}
|
||||
}
|
||||
|
||||
class AddRemoveButtonState extends State<AddRemoveButton> {
|
||||
int _qty;
|
||||
var zeroColor = const Color(0xFFEFEFEF);
|
||||
var qtyColor = const Color(0xFFFF6666);
|
||||
var zeroFontColor = const Color(0xFF888888);
|
||||
var qtyFontColor = const Color(0xFFFFFFFF);
|
||||
|
||||
var d = 1;
|
||||
|
||||
CartInfo cartInfo;
|
||||
|
||||
GlobalKey startKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.product.leftNum == null) {
|
||||
_qty = 0;
|
||||
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
|
||||
if (cartInfo != null) {
|
||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
||||
if (cartInfo.productList[i].product.id == widget.product.id
|
||||
&& cartInfo.productList[i].unitPrice == 0.0) {
|
||||
_qty = cartInfo.productList[i].quantity.round();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 5.0, bottom: 5.0, left: 32.0, right: 32.0),
|
||||
child: Text(
|
||||
'x$_qty'
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.product.leftNum <= 0) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 8.0, right: 8.0),
|
||||
child: Text(
|
||||
S.of(context).out_of_stock,
|
||||
style: TextStyle(
|
||||
fontSize: 8.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(3.0),
|
||||
topRight: Radius.circular(3.0),
|
||||
bottomLeft: Radius.circular(3.0),
|
||||
bottomRight: Radius.circular(3.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (widget.addToBasket) {
|
||||
_qty = 0;
|
||||
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
|
||||
if (cartInfo != null) {
|
||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
||||
if (cartInfo.productList[i].product.id == widget.product.id) {
|
||||
_qty = cartInfo.productList[i].quantity.round();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_qty > 0) {
|
||||
return Badge(
|
||||
badgeContent: Text(
|
||||
'$_qty',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17.0,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
position: BadgePosition.topEnd(top: -15, end: -10),
|
||||
badgeColor: Colors.lightBlueAccent,
|
||||
child: RaisedButton.icon(
|
||||
padding: EdgeInsets.only(left: 32, right: 32, top: 20, bottom: 20),
|
||||
elevation: 2.0,
|
||||
shape: new RoundedRectangleBorder(
|
||||
borderRadius: new BorderRadius.circular(10.0),
|
||||
),
|
||||
color: Colors.redAccent,
|
||||
icon: Icon(
|
||||
Icons.shopping_basket_outlined,
|
||||
size: 32.0,
|
||||
color: Colors.yellow,
|
||||
),
|
||||
label: Text(
|
||||
S.of(context).add_to_basket,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
_addToCart(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return RaisedButton.icon(
|
||||
padding: EdgeInsets.only(left: 32, right: 32, top: 20, bottom: 20),
|
||||
elevation: 2.0,
|
||||
shape: new RoundedRectangleBorder(
|
||||
borderRadius: new BorderRadius.circular(10.0),
|
||||
),
|
||||
color: Colors.redAccent,
|
||||
icon: Icon(
|
||||
Icons.shopping_basket_outlined,
|
||||
size: 32.0,
|
||||
color: Colors.yellow,
|
||||
),
|
||||
label: Text(
|
||||
S.of(context).add_to_basket,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
_addToCart(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}else if (widget.addOnly) {
|
||||
return Container(
|
||||
key: startKey,
|
||||
padding: EdgeInsets.all(0.0),
|
||||
child: GestureDetector(
|
||||
child: Icon(
|
||||
Icons.add_circle,
|
||||
size: 24.0,
|
||||
),
|
||||
onTap: () {
|
||||
_addToCart(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
_qty = 0;
|
||||
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
|
||||
if (cartInfo != null) {
|
||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
||||
if (cartInfo.productList[i].product.id == widget.product.id) {
|
||||
_qty = cartInfo.productList[i].quantity.round();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (widget.product.productAttributes != null &&
|
||||
widget.product.productAttributes.length > 0 && widget.cartLineItemIndex == -1) {
|
||||
return new Row(
|
||||
key: startKey,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
new GestureDetector(
|
||||
child: new Container(
|
||||
width: 60.0,
|
||||
child: new Center(
|
||||
child: new Text(
|
||||
'$_qty',
|
||||
style: new TextStyle(
|
||||
fontSize: 13.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _qty > 0 ? qtyFontColor : zeroFontColor
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: _qty > 0 ? qtyColor : zeroColor,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10.0),
|
||||
topRight: Radius.circular(10.0),
|
||||
bottomLeft: Radius.circular(10.0),
|
||||
bottomRight: Radius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_addToCart(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return new Row(
|
||||
key: startKey,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
new GestureDetector(
|
||||
child: new Container(
|
||||
width: 30.0,
|
||||
child: new Center(
|
||||
child: new Text(
|
||||
'$_qty',
|
||||
style: new TextStyle(
|
||||
fontSize: 13.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _qty > 0 ? qtyFontColor : zeroFontColor
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: _qty > 0 ? qtyColor : zeroColor,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10.0),
|
||||
bottomLeft: Radius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_addToCart(context);
|
||||
},
|
||||
),
|
||||
new GestureDetector(
|
||||
child: new Container(
|
||||
width: 30.0,
|
||||
child: new Center(
|
||||
child: new Text(
|
||||
'-',
|
||||
style: new TextStyle(
|
||||
fontSize: 13.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFABABAB),
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(10.0),
|
||||
bottomRight: Radius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (_qty > 0) {
|
||||
_removeFromCart(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _addToCart(BuildContext context) {
|
||||
if (widget.cartLineItemIndex != -1) {
|
||||
if (cartInfo.productList[widget.cartLineItemIndex].quantity + 1.0 > widget.product.leftNum) {
|
||||
Fluttertoast.showToast(
|
||||
msg: S.of(context).product_insufficient,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white
|
||||
);
|
||||
} else {
|
||||
cartInfo.productList[widget.cartLineItemIndex].quantity += 1.0;
|
||||
Utils.addSubproductQty(cartInfo, cartInfo.productList[widget.cartLineItemIndex]);
|
||||
store.dispatch(UpdateCartInfo(
|
||||
Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
|
||||
eventBus.fire(new OnCartInfoUpdated());
|
||||
}
|
||||
} else {
|
||||
if (widget.product.productAttributes.length > 0) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) =>
|
||||
new AttributeSelection(
|
||||
product: widget.product, business: widget.business, startKey: startKey,)),
|
||||
);
|
||||
} else {
|
||||
eventBus.fire(new OnProductWillAddToCart(widget.product, {},
|
||||
widget.product.price, widget.product.description,
|
||||
widget.business, buttonKey: startKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _removeFromCart(BuildContext context) {
|
||||
if (widget.cartLineItemIndex != -1) {
|
||||
if (cartInfo.productList[widget.cartLineItemIndex].quantity <= 1) {
|
||||
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_item),
|
||||
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: () {
|
||||
_removeCartLineItem();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
_removeCartLineItem();
|
||||
}
|
||||
} else {
|
||||
eventBus.fire(new OnProductWillRemoveFromCart(
|
||||
widget.product, -1, widget.business));
|
||||
}
|
||||
}
|
||||
|
||||
void _removeCartLineItem() {
|
||||
if (cartInfo.productList[widget.cartLineItemIndex].quantity <= 1) {
|
||||
String uuid = cartInfo.productList[widget.cartLineItemIndex].uuid;
|
||||
cartInfo.productList.removeAt(widget.cartLineItemIndex);
|
||||
Utils.removeSubproduct(cartInfo, uuid);
|
||||
} else {
|
||||
cartInfo.productList[widget.cartLineItemIndex].quantity -= 1;
|
||||
Utils.addSubproductQty(cartInfo, cartInfo.productList[widget.cartLineItemIndex], remove: true);
|
||||
}
|
||||
if (cartInfo.productList.length <= 0) {
|
||||
store.dispatch(new UpdateCartInfo(Utils.removeCartInfoFromCartInfoList(store.state.cartInfos, cartInfo)));
|
||||
} else {
|
||||
store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
|
||||
}
|
||||
eventBus.fire(new OnCartInfoUpdated());
|
||||
}
|
||||
|
||||
@override
|
||||
void setState(VoidCallback fn) {
|
||||
if(mounted) {
|
||||
super.setState(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
lib/widgets/general/animation_point_manager.dart
Normal file
111
lib/widgets/general/animation_point_manager.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'parabolic_animation_widget.dart';
|
||||
import 'popup_animation_widget.dart';
|
||||
|
||||
class AnimationPointManager {
|
||||
List<AnimatedWidget> list = [];
|
||||
static AnimationController controller1;
|
||||
static AnimationController controller2;
|
||||
|
||||
Future<void> addParabolicAniamtion({
|
||||
@required TickerProvider vsync,
|
||||
@required GlobalKey stackKey,
|
||||
@required GlobalKey startKey,
|
||||
@required GlobalKey endKey,
|
||||
@required Duration duration,
|
||||
@required AnimationStatusListener statusListener,
|
||||
Color color = Colors.red,
|
||||
double size = 20,
|
||||
Offset startAdjustOffset = Offset.zero,
|
||||
Offset endAdjustOffset = Offset.zero,
|
||||
}) async {
|
||||
controller1 = createController(vsync, duration);
|
||||
Animation animation = createAnimation(controller1);
|
||||
|
||||
AnimatedWidget animatedWidget = ParabolicAnimationWidget(
|
||||
animation: animation,
|
||||
stackKey: stackKey,
|
||||
startKey: startKey,
|
||||
endKey: endKey,
|
||||
size: size,
|
||||
color: color,
|
||||
startAdjustOffset: startAdjustOffset,
|
||||
endAdjustOffset: endAdjustOffset,
|
||||
);
|
||||
list.add(animatedWidget);
|
||||
statusListener(AnimationStatus.dismissed);
|
||||
|
||||
try {
|
||||
await controller1.forward().orCancel;
|
||||
list.remove(animatedWidget);
|
||||
controller1.dispose();
|
||||
print('Controller1 disposed');
|
||||
} on TickerCanceled {
|
||||
print("Ticker Canceled");
|
||||
} catch (error) {
|
||||
print('Error: $error');
|
||||
}
|
||||
|
||||
statusListener(AnimationStatus.completed);
|
||||
}
|
||||
|
||||
Future<void> addPopupAniamtion({
|
||||
@required TickerProvider vsync,
|
||||
@required GlobalKey stackKey,
|
||||
@required GlobalKey startKey,
|
||||
@required Widget child,
|
||||
Duration duration,
|
||||
Offset popupOffset = Offset.zero,
|
||||
AnimationStatusListener statusListener,
|
||||
}) async {
|
||||
controller2 = createController(vsync, duration);
|
||||
|
||||
AnimatedWidget animatedWidget = PopupAnimationWidget(
|
||||
animation: controller2.view,
|
||||
stackKey: stackKey,
|
||||
startKey: startKey,
|
||||
child: child,
|
||||
popupOffset: popupOffset,
|
||||
);
|
||||
list.add(animatedWidget);
|
||||
statusListener(AnimationStatus.dismissed);
|
||||
|
||||
try {
|
||||
await controller2.forward().orCancel;
|
||||
await controller2.reverse().orCancel;
|
||||
list.remove(animatedWidget);
|
||||
controller2.dispose();
|
||||
print('Controller2 disposed');
|
||||
} on TickerCanceled {
|
||||
print("Ticker Canceled");
|
||||
} catch (error) {
|
||||
print('Error: $error');
|
||||
}
|
||||
|
||||
statusListener(AnimationStatus.completed);
|
||||
}
|
||||
|
||||
static AnimationController createController(
|
||||
TickerProvider vsync, Duration duration) {
|
||||
AnimationController ani = AnimationController(
|
||||
lowerBound: 0,
|
||||
upperBound: 1,
|
||||
duration: duration ?? Duration(milliseconds: 800),
|
||||
vsync: vsync);
|
||||
return ani;
|
||||
}
|
||||
|
||||
static CurvedAnimation createAnimation(controller) {
|
||||
return CurvedAnimation(parent: controller, curve: Curves.linear);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
try {
|
||||
controller1?.dispose();
|
||||
controller2?.dispose();
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
349
lib/widgets/general/attribute/check_options.dart
Normal file
349
lib/widgets/general/attribute/check_options.dart
Normal file
@@ -0,0 +1,349 @@
|
||||
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../events/eventbus.dart';
|
||||
import '../../../events/events.dart';
|
||||
import '../../../generated/l10n.dart';
|
||||
import '../../../models/product.dart';
|
||||
import '../../../models/product_option.dart';
|
||||
import '../../../utils/utils.dart';
|
||||
import 'rules.dart';
|
||||
|
||||
import 'options_base.dart';
|
||||
|
||||
class CheckOptions extends OptionsBase {
|
||||
CheckOptions({@required Product product, @required int index, @required Map<String, dynamic> selections})
|
||||
: super(product: product, index: index, selections: selections);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new CheckOptionsState();
|
||||
}
|
||||
}
|
||||
|
||||
class CheckOptionsState extends OptionsBaseState<CheckOptions> {
|
||||
Map<String, dynamic> optionsState = new Map<String, dynamic>();
|
||||
int thisLimitQty = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
Widget w = Center(
|
||||
child: Text('Error'),
|
||||
);
|
||||
try {
|
||||
w = _getOptionWidget();
|
||||
} catch (error, stacktrace) {
|
||||
print('Error: $error, Trace: $stacktrace');
|
||||
}
|
||||
|
||||
return new ListView.builder(
|
||||
itemCount: 2,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return new Container(
|
||||
padding: new EdgeInsets.all(10.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
S.of(context).check_option_select_token(product.productAttributes[this.index].name),
|
||||
style: new TextStyle(
|
||||
fontSize: 12.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
new Text(
|
||||
product.productAttributes[this.index].required ? S.of(context).check_option_is_required : S.of(context).check_option_is_optional,
|
||||
style: new TextStyle(
|
||||
fontSize: 10.0,
|
||||
color: new Color(0xFF999999)
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
product = widget.product;
|
||||
selections = widget.selections;
|
||||
index = widget.index;
|
||||
});
|
||||
eventBus.on<OnAttributePageChanged>().listen((event) {
|
||||
setState(() {
|
||||
index = event.index;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) {
|
||||
Map<String, dynamic> opt = {
|
||||
'name': name,
|
||||
'quantity': quantity,
|
||||
'adjust_amount': adjustAmount
|
||||
};
|
||||
var cloneSelections = json.decode(json.encode(selections));
|
||||
int idx = Utils.selectionsContains(cloneSelections, product.productAttributes[index].name, name);
|
||||
if (idx != -1) {
|
||||
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).removeAt(idx);
|
||||
} else if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())) {
|
||||
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).add(opt);
|
||||
} else {
|
||||
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
|
||||
}
|
||||
|
||||
if (idx != -1 && (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).length == 0) {
|
||||
cloneSelections.remove(product.productAttributes[index].name.toUpperCase());
|
||||
}
|
||||
|
||||
setOptionsStateDisabled(product.productAttributes[index].name, false);
|
||||
|
||||
setState(() {
|
||||
selections = cloneSelections;
|
||||
});
|
||||
eventBus.fire(new OnAttributeSelectionsChanged(selections));
|
||||
}
|
||||
|
||||
Widget _getOptionWidget() {
|
||||
var row = Wrap(
|
||||
children: <Widget>[],
|
||||
);
|
||||
|
||||
List<ProductOption> productOptions = product.productAttributes[index].productOptions;
|
||||
|
||||
if (!optionsState.containsKey(product.productAttributes[index].name)) {
|
||||
List<Map<String, dynamic>> optionState = [];
|
||||
for (var i = 0; i < productOptions.length; i++) {
|
||||
optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'check': false});
|
||||
}
|
||||
optionsState[product.productAttributes[index].name] = optionState;
|
||||
}
|
||||
|
||||
if (selections.containsKey(product.productAttributes[index].name.toUpperCase())) {
|
||||
|
||||
Map<String, dynamic> attrExtraJson = Utils.stringToJson(
|
||||
product.productAttributes[index].extra);
|
||||
if (attrExtraJson != null) {
|
||||
var selectLimitIfFieldEqualsTo = Rule.getRule(
|
||||
attrExtraJson, Rule.RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO);
|
||||
if (selectLimitIfFieldEqualsTo != null) {
|
||||
if (selectLimitIfFieldEqualsTo is List) {
|
||||
for (var b = 0; b < (selectLimitIfFieldEqualsTo as List).length; b++) {
|
||||
Map<String, dynamic> selectLimitIfFieldEqualsTo1 = selectLimitIfFieldEqualsTo[b];
|
||||
if (selectLimitIfFieldEqualsTo1.containsKey(
|
||||
Rule.RULE_KEY_FORCE_LIMITED)) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo1[Rule
|
||||
.RULE_KEY_FORCE_LIMITED];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
|
||||
if (selectLimitIfFieldEqualsTo1.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0])) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo1[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0]];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectLimitIfFieldEqualsTo.containsKey(
|
||||
Rule.RULE_KEY_FORCE_LIMITED)) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo[Rule
|
||||
.RULE_KEY_FORCE_LIMITED];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
|
||||
if (selectLimitIfFieldEqualsTo.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0])) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0]];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < productOptions.length; i++) {
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(
|
||||
productOptions[i].extra);
|
||||
if (extraJson != null) {
|
||||
Map<String, dynamic> exclusiveRule = Rule.getRule(
|
||||
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
|
||||
Map<String, dynamic> multiItemRule = Rule.getRule(extraJson, Rule.RULE_ACTUAL_QTY_IS);
|
||||
if (exclusiveRule != null) {
|
||||
if (thisLimitQty > 0 && !_checkOptionIsCheck(productOptions[i].name)) {
|
||||
optionsState[product.productAttributes[index].name][i]['disabled'] = true;
|
||||
} else {
|
||||
if (_checkOptionIsCheck(productOptions[i].name)) {
|
||||
setOptionsStateDisabled(
|
||||
product.productAttributes[index].name, true);
|
||||
optionsState[product.productAttributes[index]
|
||||
.name][i]['disabled'] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (multiItemRule != null) {
|
||||
if (_checkOptionIsCheck(productOptions[i].name)) {
|
||||
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
} else {
|
||||
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
|
||||
optionsState[product.productAttributes[index]
|
||||
.name][i]['disabled'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> optionState = optionsState[product.productAttributes[index].name];
|
||||
for (var i = 0; i < optionState.length; i++) {
|
||||
Widget optionWidget = _getOptionCheck(
|
||||
product.productAttributes[index].productOptions[i], optionState[i]['disabled'], i);
|
||||
row.children.add(optionWidget);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
void disableOptionIfNotSelected() {
|
||||
setOptionsStateDisabled(product.productAttributes[index].name, true);
|
||||
for (var i = 0; i < optionsState[product.productAttributes[index].name].length; i++) {
|
||||
if (Utils.selectionsContains(selections, product.productAttributes[index].name, optionsState[product.productAttributes[index].name][i]['name']) != -1) {
|
||||
optionsState[product.productAttributes[index].name][i]['disabled'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _checkOptionIsCheck(String name) {
|
||||
if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget _getOptionCheck(ProductOption productOption, bool optDisabled, int optIndex) {
|
||||
bool disabled = optDisabled;
|
||||
bool check = _checkOptionIsCheck(productOption.name);
|
||||
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
|
||||
// Utils.jsonPrettyPrint(extraJson);
|
||||
|
||||
double extraAdjustAmount = 0.0;
|
||||
|
||||
if (extraJson != null) {
|
||||
var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE);
|
||||
if (sizeBaseAdjustment != null) {
|
||||
if (sizeBaseAdjustment is List) {
|
||||
for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) {
|
||||
Map<String, dynamic> sizeBaseAdjustment1 = sizeBaseAdjustment[i];
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) {
|
||||
extraAdjustAmount += sizeBaseAdjustment1[attValues[0]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) {
|
||||
extraAdjustAmount += sizeBaseAdjustment[attValues[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO);
|
||||
if (disabledRule != null) {
|
||||
if (disabledRule is List) {
|
||||
for (var i = 0; i < (disabledRule as List).length; i++) {
|
||||
Map<String, dynamic> disabledRule1 = disabledRule[i];
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new GestureDetector(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 70.0,
|
||||
decoration: BoxDecoration(
|
||||
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
|
||||
shape: BoxShape.rectangle,
|
||||
border: new Border.all(
|
||||
color: check ? selectedBorderColor : deselectBorderColor,
|
||||
width: 1.0,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
padding: new EdgeInsets.all(4.0),
|
||||
margin: new EdgeInsets.all(10.0).copyWith(right: 0.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
productOption.name,
|
||||
style: new TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: check ? selectedTextColor : deselectTextColor,
|
||||
),
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
new Text(
|
||||
(productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '',
|
||||
style: new TextStyle(
|
||||
fontSize: 11.0,
|
||||
color: check ? selectedTextColor : new Color(0xFFABABAB),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () => disabled ? null : _onOptionTappedCallback(
|
||||
productOption.name, 0, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
|
||||
);
|
||||
}
|
||||
|
||||
void setOptionsStateDisabled(String attrName, bool disabled) {
|
||||
if (optionsState.containsKey(attrName)) {
|
||||
List<Map<String, dynamic>> optionState = optionsState[attrName];
|
||||
for (var i = 0; i < optionState.length; i++) {
|
||||
optionState[i]['disabled'] = disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
lib/widgets/general/attribute/options_base.dart
Normal file
39
lib/widgets/general/attribute/options_base.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/models/product.dart';
|
||||
|
||||
typedef void OnOptionTapped(String name, int quantity, double adjustAmount);
|
||||
|
||||
abstract class OptionsBase extends StatefulWidget {
|
||||
final Product product;
|
||||
final int index;
|
||||
final Map<String, dynamic> selections;
|
||||
OptionsBase({@required Product product, @required int index, @required Map<String, dynamic> selections})
|
||||
: product = product,
|
||||
index = index,
|
||||
selections = selections;
|
||||
}
|
||||
|
||||
abstract class OptionsBaseState<Base extends OptionsBase> extends State<Base> {
|
||||
Product product;
|
||||
Map<String, dynamic> selections;
|
||||
int index;
|
||||
|
||||
final Color disabledBackgroundColor = new Color(0xFFBCBCBC);
|
||||
|
||||
final Color deselectBackgroundColor = new Color(0x00000000);
|
||||
final Color deselectTextColor = new Color(0xFF333333);
|
||||
final Color deselectBorderColor = new Color(0xFF888888);
|
||||
|
||||
final Color selectedBackgroundColor = new Color(0xFFFF8908);
|
||||
final Color selectedTextColor = new Color(0xFFFFFFFF);
|
||||
final Color selectedBorderColor = new Color(0xFF444444);
|
||||
|
||||
@override
|
||||
void setState(VoidCallback fn) {
|
||||
if(mounted) {
|
||||
super.setState(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
422
lib/widgets/general/attribute/qty_options.dart
Normal file
422
lib/widgets/general/attribute/qty_options.dart
Normal file
@@ -0,0 +1,422 @@
|
||||
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../events/eventbus.dart';
|
||||
import '../../../events/events.dart';
|
||||
import '../../../generated/l10n.dart';
|
||||
import '../../../models/product.dart';
|
||||
import '../../../models/product_option.dart';
|
||||
import '../../../utils/utils.dart';
|
||||
import 'options_base.dart';
|
||||
import 'rules.dart';
|
||||
|
||||
class QtyOptions extends OptionsBase {
|
||||
QtyOptions({@required Product product, @required int index, @required Map<String, dynamic> selections})
|
||||
: super(product: product, index: index, selections: selections);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new QtyOptionsState();
|
||||
}
|
||||
}
|
||||
|
||||
class QtyOptionsState extends OptionsBaseState<QtyOptions> {
|
||||
Map<String, dynamic> optionsState = new Map<String, dynamic>();
|
||||
int thisLimitQty = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return new ListView.builder(
|
||||
itemCount: 2,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return new Container(
|
||||
padding: new EdgeInsets.all(10.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
S.of(context).check_option_select_token(product.productAttributes[this.index].name),
|
||||
style: new TextStyle(
|
||||
fontSize: 12.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
new Text(
|
||||
product.productAttributes[this.index].required ? S.of(context).check_option_is_required : S.of(context).check_option_is_optional,
|
||||
style: new TextStyle(
|
||||
fontSize: 10.0,
|
||||
color: new Color(0xFF999999)
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return _getOptionWidget();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
product = widget.product;
|
||||
selections = widget.selections;
|
||||
index = widget.index;
|
||||
});
|
||||
eventBus.on<OnAttributePageChanged>().listen((event) {
|
||||
setState(() {
|
||||
index = event.index;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) {
|
||||
Map<String, dynamic> opt = {
|
||||
'name': name,
|
||||
'quantity': quantity,
|
||||
'adjust_amount': adjustAmount
|
||||
};
|
||||
var cloneSelections = json.decode(json.encode(selections));
|
||||
int idx = Utils.selectionsContains(cloneSelections, product.productAttributes[index].name, name);
|
||||
if (idx != -1) {
|
||||
if (quantity == 1) {
|
||||
cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] += 1;
|
||||
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'];
|
||||
} else {
|
||||
if (cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] - 1 > 0) {
|
||||
cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] -= 1;
|
||||
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'];
|
||||
} else {
|
||||
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).removeAt(idx);
|
||||
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 0;
|
||||
}
|
||||
}
|
||||
} else if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())) {
|
||||
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).add(opt);
|
||||
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 1;
|
||||
} else {
|
||||
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
|
||||
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 1;
|
||||
}
|
||||
|
||||
if (idx != -1 && (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).length == 0) {
|
||||
cloneSelections.remove(product.productAttributes[index].name.toUpperCase());
|
||||
}
|
||||
|
||||
setOptionsStateDisabled(product.productAttributes[index].name, false);
|
||||
|
||||
setState(() {
|
||||
selections = cloneSelections;
|
||||
});
|
||||
eventBus.fire(new OnAttributeSelectionsChanged(selections));
|
||||
}
|
||||
|
||||
Widget _getOptionWidget() {
|
||||
var row = Wrap(
|
||||
children: <Widget>[],
|
||||
);
|
||||
|
||||
List<ProductOption> productOptions = product.productAttributes[index].productOptions;
|
||||
|
||||
if (!optionsState.containsKey(product.productAttributes[index].name)) {
|
||||
List<Map<String, dynamic>> optionState = [];
|
||||
for (var i = 0; i < productOptions.length; i++) {
|
||||
int qty = 0;
|
||||
int idx = Utils.selectionsContains(selections, product.productAttributes[index].name, productOptions[i].name);
|
||||
if (idx != -1) {
|
||||
qty = selections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'];
|
||||
}
|
||||
optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'quantity': qty, 'check': false});
|
||||
}
|
||||
optionsState[product.productAttributes[index].name] = optionState;
|
||||
}
|
||||
|
||||
if (selections.containsKey(product.productAttributes[index].name.toUpperCase())) {
|
||||
Map<String, dynamic> attrExtraJson = Utils.stringToJson(
|
||||
product.productAttributes[index].extra);
|
||||
if (attrExtraJson != null) {
|
||||
var selectLimitIfFieldEqualsTo = Rule.getRule(
|
||||
attrExtraJson, Rule.RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO);
|
||||
if (selectLimitIfFieldEqualsTo != null) {
|
||||
if (selectLimitIfFieldEqualsTo is List) {
|
||||
for (var b = 0; b < (selectLimitIfFieldEqualsTo as List).length; b++) {
|
||||
Map<String, dynamic> selectLimitIfFieldEqualsTo1 = selectLimitIfFieldEqualsTo[b];
|
||||
if (selectLimitIfFieldEqualsTo1.containsKey(
|
||||
Rule.RULE_KEY_FORCE_LIMITED)) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo1[Rule
|
||||
.RULE_KEY_FORCE_LIMITED];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
|
||||
if (selectLimitIfFieldEqualsTo1.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0])) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo1[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0]];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectLimitIfFieldEqualsTo.containsKey(
|
||||
Rule.RULE_KEY_FORCE_LIMITED)) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo[Rule
|
||||
.RULE_KEY_FORCE_LIMITED];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
|
||||
if (selectLimitIfFieldEqualsTo.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0])) {
|
||||
int limitQty = selectLimitIfFieldEqualsTo[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0]];
|
||||
thisLimitQty = limitQty;
|
||||
if ((selections[product.productAttributes[index].name
|
||||
.toUpperCase()] as List).length >= limitQty) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < productOptions.length; i++) {
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(
|
||||
productOptions[i].extra);
|
||||
if (extraJson != null) {
|
||||
Map<String, dynamic> exclusiveRule = Rule.getRule(
|
||||
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
|
||||
Map<String, dynamic> multiItemRule = Rule.getRule(extraJson, Rule.RULE_ACTUAL_QTY_IS);
|
||||
if (exclusiveRule != null) {
|
||||
if (thisLimitQty > 0 && !_checkOptionIsCheck(productOptions[i].name)) {
|
||||
optionsState[product.productAttributes[index].name][i]['disabled'] = true;
|
||||
} else {
|
||||
if (_checkOptionIsCheck(productOptions[i].name)) {
|
||||
setOptionsStateDisabled(
|
||||
product.productAttributes[index].name, true);
|
||||
optionsState[product.productAttributes[index]
|
||||
.name][i]['disabled'] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (multiItemRule != null) {
|
||||
if (_checkOptionIsCheck(productOptions[i].name)) {
|
||||
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
|
||||
disableOptionIfNotSelected();
|
||||
}
|
||||
} else {
|
||||
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
|
||||
optionsState[product.productAttributes[index]
|
||||
.name][i]['disabled'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> optionState = optionsState[product.productAttributes[index].name];
|
||||
for (var i = 0; i < optionState.length; i++) {
|
||||
Widget optionWidget = _getOptionQty(
|
||||
product.productAttributes[index].productOptions[i], optionState[i]['disabled'], optionState[i]['quantity'], i);
|
||||
row.children.add(optionWidget);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
void disableOptionIfNotSelected() {
|
||||
setOptionsStateDisabled(product.productAttributes[index].name, true);
|
||||
for (var i = 0; i < optionsState[product.productAttributes[index].name].length; i++) {
|
||||
if (Utils.selectionsContains(selections, product.productAttributes[index].name, optionsState[product.productAttributes[index].name][i]['name']) != -1) {
|
||||
optionsState[product.productAttributes[index].name][i]['disabled'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _checkOptionIsCheck(String name) {
|
||||
if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget _getOptionQty(ProductOption productOption, bool optDisabled, int quantity, int optIndex) {
|
||||
bool disabled = optDisabled;
|
||||
bool check = _checkOptionIsCheck(productOption.name);
|
||||
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
|
||||
// Utils.jsonPrettyPrint(extraJson);
|
||||
|
||||
double extraAdjustAmount = 0.0;
|
||||
|
||||
if (extraJson != null) {
|
||||
var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE);
|
||||
if (sizeBaseAdjustment != null) {
|
||||
if (sizeBaseAdjustment is List) {
|
||||
for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) {
|
||||
Map<String, dynamic> sizeBaseAdjustment1 = sizeBaseAdjustment[i];
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) {
|
||||
extraAdjustAmount += sizeBaseAdjustment1[attValues[0]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) {
|
||||
extraAdjustAmount += sizeBaseAdjustment[attValues[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO);
|
||||
if (disabledRule != null) {
|
||||
if (disabledRule is List) {
|
||||
for (var i = 0; i < (disabledRule as List).length; i++) {
|
||||
Map<String, dynamic> disabledRule1 = disabledRule[i];
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
decoration: BoxDecoration(
|
||||
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
|
||||
shape: BoxShape.rectangle,
|
||||
border: new Border.all(
|
||||
color: check ? selectedBorderColor : deselectBorderColor,
|
||||
width: 1.0,
|
||||
),
|
||||
// borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10.0),
|
||||
topRight: Radius.circular(10.0),
|
||||
),
|
||||
),
|
||||
|
||||
margin: new EdgeInsets.all(10.0).copyWith(right: 0.0),
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new GestureDetector(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 70.0,
|
||||
padding: new EdgeInsets.all(4.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
productOption.name,
|
||||
style: new TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: check ? selectedTextColor : deselectTextColor,
|
||||
),
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
new Text(
|
||||
(productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '',
|
||||
style: new TextStyle(
|
||||
fontSize: 11.0,
|
||||
color: check ? selectedTextColor : new Color(0xFFABABAB),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () => disabled ? null : _onOptionTappedCallback(
|
||||
productOption.name, 1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
|
||||
),
|
||||
new Container(
|
||||
width: 100.0,
|
||||
height: 28.0,
|
||||
decoration: BoxDecoration(
|
||||
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
|
||||
shape: BoxShape.rectangle,
|
||||
border: new Border(
|
||||
top: new BorderSide(
|
||||
color: check ? selectedBorderColor : deselectBorderColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: new Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
new Expanded(
|
||||
child: new GestureDetector(
|
||||
child: new Container(
|
||||
color: new Color(0x00000000),
|
||||
width: 49.0,
|
||||
height: 28.0,
|
||||
child: new Center(
|
||||
child: new Text(
|
||||
'${quantity}',
|
||||
style: new TextStyle(
|
||||
color: check ? selectedTextColor : new Color(0xFFABABAB),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () => disabled ? null : _onOptionTappedCallback(
|
||||
productOption.name, 1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: new GestureDetector(
|
||||
child: new Container(
|
||||
color: disabledBackgroundColor,
|
||||
width: 49.0,
|
||||
height: 28.0,
|
||||
child: new Center(
|
||||
child: new Text('-')
|
||||
),
|
||||
),
|
||||
onTap: () => (disabled || quantity == 0) ? null : _onOptionTappedCallback(
|
||||
productOption.name, -1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void setOptionsStateDisabled(String attrName, bool disabled) {
|
||||
if (optionsState.containsKey(attrName)) {
|
||||
List<Map<String, dynamic>> optionState = optionsState[attrName];
|
||||
for (var i = 0; i < optionState.length; i++) {
|
||||
optionState[i]['disabled'] = disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
261
lib/widgets/general/attribute/radio_options.dart
Normal file
261
lib/widgets/general/attribute/radio_options.dart
Normal file
@@ -0,0 +1,261 @@
|
||||
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../events/eventbus.dart';
|
||||
import '../../../events/events.dart';
|
||||
import '../../../generated/l10n.dart';
|
||||
import '../../../models/product.dart';
|
||||
import '../../../models/product_option.dart';
|
||||
import '../../../utils/utils.dart';
|
||||
|
||||
import 'options_base.dart';
|
||||
import 'rules.dart';
|
||||
|
||||
class RadioOptions extends OptionsBase {
|
||||
RadioOptions({@required Product product, @required int index, @required Map<String, dynamic> selections})
|
||||
: super(product: product, index: index, selections: selections);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new RadioOptionsState();
|
||||
}
|
||||
}
|
||||
|
||||
class RadioOptionsState extends OptionsBaseState<RadioOptions> {
|
||||
Map<String, dynamic> optionsState = new Map();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return new ListView.builder(
|
||||
itemCount: 2,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return new Container(
|
||||
padding: new EdgeInsets.all(10.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
S.of(context).radio_option_select_token(product.productAttributes[this.index].name),
|
||||
style: new TextStyle(
|
||||
fontSize: 12.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
new Text(
|
||||
product.productAttributes[this.index].required ? S.of(context).radio_option_is_required : S.of(context).radio_option_is_optional,
|
||||
style: new TextStyle(
|
||||
fontSize: 10.0,
|
||||
color: new Color(0xFF999999)
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return _getOptionWidget();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
product = widget.product;
|
||||
selections = widget.selections;
|
||||
index = widget.index;
|
||||
});
|
||||
eventBus.on<OnAttributePageChanged>().listen((event) {
|
||||
setState(() {
|
||||
index = event.index;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) {
|
||||
Map<String, dynamic> opt = {
|
||||
'name': name,
|
||||
'quantity': quantity,
|
||||
'adjust_amount': adjustAmount
|
||||
};
|
||||
var cloneSelections = json.decode(json.encode(selections));
|
||||
if (product.productAttributes[index].required) {
|
||||
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
|
||||
} else {
|
||||
if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())
|
||||
&& Utils.equalsIgnoreCase(cloneSelections[product.productAttributes[index].name.toUpperCase()][0]['name'], name)) {
|
||||
cloneSelections.remove(product.productAttributes[index].name.toUpperCase());
|
||||
} else {
|
||||
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
|
||||
if (extraJson != null) {
|
||||
Map<String, dynamic> exclusiveRule = Rule.getRule(
|
||||
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
|
||||
if (exclusiveRule != null) {
|
||||
if (_checkOptionIsCheck(productOption.name)) {
|
||||
setOptionsStateDisabled(product.productAttributes[index].name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
selections = cloneSelections;
|
||||
});
|
||||
eventBus.fire(new OnAttributeSelectionsChanged(selections));
|
||||
}
|
||||
|
||||
Widget _getOptionWidget() {
|
||||
var row = Wrap(
|
||||
children: <Widget>[],
|
||||
);
|
||||
|
||||
List<ProductOption> productOptions = product.productAttributes[index].productOptions;
|
||||
|
||||
if (!optionsState.containsKey(product.productAttributes[index].name)) {
|
||||
List<Map<String, dynamic>> optionState = [];
|
||||
for (var i = 0; i < productOptions.length; i++) {
|
||||
optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'check': false});
|
||||
}
|
||||
optionsState[product.productAttributes[index].name] = optionState;
|
||||
}
|
||||
|
||||
for (var i = 0; i < productOptions.length; i++) {
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(productOptions[i].extra);
|
||||
if (extraJson != null) {
|
||||
Map<String, dynamic> exclusiveRule = Rule.getRule(
|
||||
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
|
||||
if (exclusiveRule != null) {
|
||||
if (_checkOptionIsCheck(productOptions[i].name)) {
|
||||
setOptionsStateDisabled(product.productAttributes[index].name, true);
|
||||
optionsState[product.productAttributes[index].name][i]['disabled'] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> optionState = optionsState[product.productAttributes[index].name];
|
||||
for (var i = 0; i < optionState.length; i++) {
|
||||
Widget optionWidget = _getOptionRadio(
|
||||
product.productAttributes[index].productOptions[i], optionState[i]['disabled'], i);
|
||||
row.children.add(optionWidget);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
bool _checkOptionIsCheck(String name) {
|
||||
if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget _getOptionRadio(ProductOption productOption, bool optDisabled, int optIndex) {
|
||||
bool disabled = optDisabled;
|
||||
bool check = _checkOptionIsCheck(productOption.name);
|
||||
|
||||
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
|
||||
// Utils.jsonPrettyPrint(extraJson);
|
||||
|
||||
double extraAdjustAmount = 0.0;
|
||||
|
||||
if (extraJson != null) {
|
||||
var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE);
|
||||
if (sizeBaseAdjustment != null) {
|
||||
if (sizeBaseAdjustment is List) {
|
||||
for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) {
|
||||
Map<String, dynamic> sizeBaseAdjustment1 = sizeBaseAdjustment[i];
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) {
|
||||
extraAdjustAmount += sizeBaseAdjustment1[attValues[0]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) {
|
||||
extraAdjustAmount += sizeBaseAdjustment[attValues[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO);
|
||||
if (disabledRule != null) {
|
||||
if (disabledRule is List) {
|
||||
for (var i = 0; i < (disabledRule as List).length; i++) {
|
||||
Map<String, dynamic> disabledRule1 = disabledRule[i];
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]);
|
||||
if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new GestureDetector(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 70.0,
|
||||
decoration: BoxDecoration(
|
||||
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
|
||||
shape: BoxShape.rectangle,
|
||||
border: new Border.all(
|
||||
color: check ? selectedBorderColor : deselectBorderColor,
|
||||
width: 1.0,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
padding: new EdgeInsets.all(4.0),
|
||||
margin: new EdgeInsets.all(10.0).copyWith(right: 0.0),
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
productOption.name,
|
||||
style: new TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: check ? selectedTextColor : deselectTextColor,
|
||||
),
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
new Text(
|
||||
(productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '',
|
||||
style: new TextStyle(
|
||||
fontSize: 11.0,
|
||||
color: check ? selectedTextColor : new Color(0xFFABABAB),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () => disabled ? null : _onOptionTappedCallback(
|
||||
productOption.name, 0, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
|
||||
);
|
||||
}
|
||||
|
||||
void setOptionsStateDisabled(String attrName, bool disabled) {
|
||||
if (optionsState.containsKey(attrName)) {
|
||||
Map<String, dynamic> optionState = optionsState[attrName];
|
||||
for (var i = 0; i < optionState.length; i++) {
|
||||
optionState[i]['disabled'] = disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
lib/widgets/general/attribute/rules.dart
Normal file
27
lib/widgets/general/attribute/rules.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
class Rule {
|
||||
static final String rulesName = 'rules';
|
||||
static final String RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE = "adjustment-based-on-selected-field-value";
|
||||
static final String RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO = "select-limit-if-field-equals-to";
|
||||
static final String RULE_DISABLED_IF_FIELD_EQUALS_TO = "disabled-if-field-equals-to";
|
||||
static final String RULE_EXCLUSIVE_SELECTION = "exclusive-selection";
|
||||
static final String RULE_MAX_QTY_LIMIT = "max-qty-limit";
|
||||
static final String RULE_ACTUAL_QTY_IS = "actual-quantity-is";
|
||||
|
||||
static final String RULE_KEY_FORCE_LIMITED = 'force_limited';
|
||||
static final String RULE_KEY_FIELD_KEY = 'field-key';
|
||||
|
||||
static dynamic getRule(Map<String, dynamic> json, String ruleName) {
|
||||
if (json != null && json.containsKey(rulesName)) {
|
||||
if ((json[rulesName] as Map).containsKey(ruleName)) {
|
||||
if (json[rulesName][ruleName] is bool) {
|
||||
return {ruleName: true};
|
||||
} else if (json[rulesName][ruleName] is int) {
|
||||
return {ruleName: json[rulesName][ruleName]};
|
||||
}
|
||||
return json[rulesName][ruleName];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ class BottomNavState extends State<BottomNav> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 50.0,
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
|
||||
height: 70.0,
|
||||
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xff232323),
|
||||
),
|
||||
|
||||
192
lib/widgets/general/breadcrumbs.dart
Normal file
192
lib/widgets/general/breadcrumbs.dart
Normal file
@@ -0,0 +1,192 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../routes.dart';
|
||||
|
||||
class BreadCrumbs extends StatelessWidget {
|
||||
final List<BreadCrumb> breadCrumbs;
|
||||
final bool hasBack;
|
||||
const BreadCrumbs(this.hasBack, {this.breadCrumbs, Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> widgets = [];
|
||||
if (this.hasBack) {
|
||||
widgets.add(Container(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.arrow_back_ios,
|
||||
size: 16.0,
|
||||
color: Colors.black38,
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 0.0, right: 4.0),
|
||||
child: Text(
|
||||
S.of(context).back,
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Routes.router.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
if (breadCrumbs != null) {
|
||||
for (int i = 0; i < breadCrumbs.length; i++) {
|
||||
BreadCrumb breadCrumb = breadCrumbs[i];
|
||||
if (breadCrumb.text == null && breadCrumb.item != null) {
|
||||
if (breadCrumb.onTap == null) {
|
||||
widgets.add(breadCrumb.item);
|
||||
} else {
|
||||
widgets.add(MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
child: breadCrumb.item,
|
||||
onTap: breadCrumb.onTap,
|
||||
),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if (breadCrumb.route != null) {
|
||||
widgets.add(Container(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 0.0),
|
||||
child: Icon(
|
||||
Icons.circle,
|
||||
size: 6.0,
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 4.0, right: 4.0),
|
||||
child: breadCrumb.icon != null ?
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
breadCrumb.icon,
|
||||
size: 16.0,
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
Text(
|
||||
breadCrumb.text,
|
||||
style: TextStyle(
|
||||
color: Colors.blueAccent,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
)
|
||||
],
|
||||
) :
|
||||
Text(
|
||||
breadCrumb.text,
|
||||
style: TextStyle(
|
||||
color: Colors.blueAccent,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Routes.router.navigateTo(context, breadCrumb.route);
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
} else {
|
||||
widgets.add(Container(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 0.0),
|
||||
child: Icon(
|
||||
Icons.circle,
|
||||
size: 6.0,
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 4.0, right: 4.0),
|
||||
child: breadCrumb.icon != null ?
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
breadCrumb.icon,
|
||||
size: 16.0,
|
||||
color: Colors.lightBlueAccent,
|
||||
),
|
||||
Text(
|
||||
breadCrumb.text,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
],
|
||||
) :
|
||||
Text(
|
||||
breadCrumb.text,
|
||||
style: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8.0, top: 8.0),
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: widgets,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BreadCrumb {
|
||||
final String text;
|
||||
final String route;
|
||||
final IconData icon;
|
||||
final Widget item;
|
||||
final Function onTap;
|
||||
|
||||
BreadCrumb(this.text, this.route, {this.icon, this.item, this.onTap});
|
||||
}
|
||||
146
lib/widgets/general/carousel.dart
Normal file
146
lib/widgets/general/carousel.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'style.dart';
|
||||
|
||||
class Carousel extends StatefulWidget {
|
||||
Carousel({
|
||||
double height = 200.0,
|
||||
List<Widget> pages,
|
||||
bool autoPlay,
|
||||
Duration duration = const Duration(seconds: 2),
|
||||
Duration animationDuration = const Duration(milliseconds: 1000),
|
||||
})
|
||||
: height = height,
|
||||
pages = pages,
|
||||
autoPlay = autoPlay,
|
||||
duration = duration,
|
||||
animationDuration = animationDuration;
|
||||
|
||||
final double height;
|
||||
final List<Widget> pages;
|
||||
final bool autoPlay;
|
||||
final Duration duration;
|
||||
final Duration animationDuration;
|
||||
|
||||
@override
|
||||
createState() => new CarouselState();
|
||||
}
|
||||
|
||||
class CarouselState extends State<Carousel> {
|
||||
final _pageController = new PageController();
|
||||
|
||||
Timer _timer;
|
||||
int _currentPage = 0;
|
||||
bool reverse = false;
|
||||
GlobalKey<IndicatorState> _indicatorStateKey = new GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.autoPlay) {
|
||||
_timer = new Timer.periodic(widget.duration, (timer) {
|
||||
_pageController.animateToPage(_currentPage,
|
||||
duration: widget.animationDuration, curve: Curves.linear);
|
||||
if (!reverse) {
|
||||
_currentPage += 1;
|
||||
if (_currentPage == widget.pages.length) {
|
||||
_currentPage -= 1;
|
||||
reverse = true;
|
||||
}
|
||||
} else {
|
||||
_currentPage -= 1;
|
||||
if (_currentPage < 0) {
|
||||
_currentPage += 1;
|
||||
reverse = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController?.dispose();
|
||||
if (_timer != null) {
|
||||
_timer.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Stack(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: widget.height,
|
||||
color: Style.backgroundColor,
|
||||
child: new PageView(
|
||||
controller: _pageController,
|
||||
children: widget.pages,
|
||||
onPageChanged: (index) {
|
||||
_currentPage = index;
|
||||
_indicatorStateKey.currentState.changeIndex(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
new Positioned(
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
bottom: 0.0,
|
||||
child: new Align(
|
||||
child: new Indicator(
|
||||
key: _indicatorStateKey,
|
||||
count: widget.pages.length,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Indicator extends StatefulWidget {
|
||||
Indicator({Key key, int count})
|
||||
: count = count,
|
||||
super(key: key);
|
||||
|
||||
final int count;
|
||||
|
||||
@override
|
||||
createState() => new IndicatorState();
|
||||
}
|
||||
|
||||
class IndicatorState extends State<Indicator> {
|
||||
int _index = 0;
|
||||
|
||||
changeIndex(int index) {
|
||||
setState(() {
|
||||
_index = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var indicators = <Widget>[];
|
||||
for (var i = 0; i < widget.count; ++i) {
|
||||
indicators.add(new Container(
|
||||
width: 5.0,
|
||||
height: 5.0,
|
||||
decoration: new BoxDecoration(
|
||||
borderRadius: new BorderRadius.all(new Radius.circular(5.0)),
|
||||
color: _index == i ? Style.primaryColor : Style.borderColor,
|
||||
),
|
||||
));
|
||||
}
|
||||
return new SizedBox(
|
||||
width: widget.count * 15.0,
|
||||
height: 30.0,
|
||||
child: new Row(
|
||||
children: indicators,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/widgets/general/double_back_to_close_app_wrapper.dart
Normal file
26
lib/widgets/general/double_back_to_close_app_wrapper.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../utils/double_back_to_close_app.dart';
|
||||
|
||||
class DoubleBackToCloseAppWrapper extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const DoubleBackToCloseAppWrapper({Key key, this.child}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DoubleBackToCloseApp(
|
||||
snackBar: SnackBar(
|
||||
content: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 24.0,
|
||||
child: Text(
|
||||
S.of(context).tap_back_again_to_exit,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../widgets/desktop/desktop_download_apps.dart';
|
||||
import '../../widgets/mobile/mobile_download_apps.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
class DownloadApps extends StatefulWidget {
|
||||
const DownloadApps({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return DownloadAppsState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DownloadAppsState extends State<DownloadApps> {
|
||||
Map<String, dynamic> data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenTypeLayout(
|
||||
mobile: MobileDownloadApps(data),
|
||||
tablet: DesktopDownloadApps(data),
|
||||
desktop: DesktopDownloadApps(data),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadData();
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
HttpUtil.httpGet('v1/get-wisetronic-download-page')
|
||||
.then((value) {
|
||||
print('$value');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
data = value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import '../../widgets/general/text_link.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../generated/l10n.dart';
|
||||
@@ -31,7 +30,7 @@ class DownloadItemState extends State<DownloadItem> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: widget.width ?? MediaQuery.of(context).size.width,
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0, right: 10.0),
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 20.0, left: 10.0, right: 10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -45,16 +44,8 @@ class DownloadItemState extends State<DownloadItem> {
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
child: (kIsWeb) ?
|
||||
Image.network(
|
||||
child: Util.showImage(
|
||||
'${widget.desc['app_icon']}',
|
||||
) :
|
||||
SvgPicture.network(
|
||||
'${widget.desc['app_icon']}',
|
||||
placeholderBuilder: (BuildContext context) => Container(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
width: iconWidth,
|
||||
height: iconWidth,
|
||||
@@ -210,7 +201,7 @@ class DownloadItemState extends State<DownloadItem> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
print('download url $downloadUrl');
|
||||
|
||||
if (downloadUrl != null && downloadUrl == 'instore') {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/widgets/desktop/desktop_navigationbar.dart';
|
||||
import 'package:flutter_wisetronic/widgets/mobile/mobile_navigationbar.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
import '../../widgets/desktop/desktop_navigationbar.dart';
|
||||
import '../../widgets/mobile/mobile_navigationbar.dart';
|
||||
import 'breadcrumbs.dart';
|
||||
|
||||
class NavigationBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
final Key key;
|
||||
final PreferredSizeWidget bottom;
|
||||
final String title;
|
||||
final bool back;
|
||||
final bool toHome;
|
||||
final bool showMe;
|
||||
final List<BreadCrumb> breadCrumbs;
|
||||
final double breadCrumbHeight;
|
||||
final Widget shoppingCart;
|
||||
|
||||
NavigationBar({Key key, PreferredSizeWidget bottom, String title, bool back})
|
||||
NavigationBar({Key key, PreferredSizeWidget bottom, String title,
|
||||
bool back, bool toHome, bool showMe, this.breadCrumbs,
|
||||
this.breadCrumbHeight, this.shoppingCart})
|
||||
: key = key,
|
||||
preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
|
||||
bottom = bottom,
|
||||
preferredSize = breadCrumbHeight != null ? Size.fromHeight(kToolbarHeight + breadCrumbHeight) :
|
||||
Size.fromHeight(kToolbarHeight),
|
||||
title = title ?? '',
|
||||
back = back ?? false;
|
||||
back = back ?? false,
|
||||
toHome = toHome ?? false,
|
||||
showMe = showMe ?? true;
|
||||
|
||||
@override
|
||||
final Size preferredSize;
|
||||
@@ -32,10 +42,17 @@ class NavigationBarState extends State<NavigationBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenTypeLayout(
|
||||
mobile: MobileNavigationBar(title: widget.title, back: widget.back,),
|
||||
tablet: DesktopNavigationBar(),
|
||||
desktop: DesktopNavigationBar(),
|
||||
mobile: MobileNavigationBar(title: widget.title, back: widget.back,
|
||||
toHome: widget.toHome, showMe: widget.showMe,),
|
||||
tablet: DesktopNavigationBar(hasBack: widget.back,
|
||||
breadCrumbs: widget.breadCrumbs, shoppingCart: widget.shoppingCart,),
|
||||
desktop: DesktopNavigationBar(hasBack: widget.back,
|
||||
breadCrumbs: widget.breadCrumbs, shoppingCart: widget.shoppingCart,),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
}
|
||||
91
lib/widgets/general/parabolic_animation_widget.dart
Normal file
91
lib/widgets/general/parabolic_animation_widget.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/physics.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ParabolicAnimationWidget extends AnimatedWidget {
|
||||
final GlobalKey stackKey;
|
||||
final GlobalKey startKey;
|
||||
final GlobalKey endKey;
|
||||
final double size;
|
||||
final Color color;
|
||||
final Offset startAdjustOffset;
|
||||
final Offset endAdjustOffset;
|
||||
|
||||
ParabolicAnimationWidget({
|
||||
@required Animation<double> animation,
|
||||
@required this.stackKey,
|
||||
@required this.startKey,
|
||||
@required this.endKey,
|
||||
this.size = 20.0,
|
||||
this.color = Colors.red,
|
||||
this.startAdjustOffset = Offset.zero,
|
||||
this.endAdjustOffset = Offset.zero,
|
||||
}) : super(listenable: animation);
|
||||
|
||||
Offset _startOffset;
|
||||
Offset _endOffset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_calPoints();
|
||||
|
||||
final Animation<double> animation = listenable;
|
||||
final double time = animation.value;
|
||||
|
||||
// 设time=1 已知两点坐标 和 初速度 可求出 加速度 a
|
||||
// double x(double time) => _x + _v * time + 0.5 * _a * time * time;
|
||||
final double initV = -400; //纵坐标初速度, 负值为向上抛
|
||||
final double acceleration =
|
||||
(_endOffset.dy - _startOffset.dy - initV) / 0.5; //求纵坐标加速度
|
||||
|
||||
final GravitySimulation spy = GravitySimulation(
|
||||
acceleration, _startOffset.dy, _endOffset.dy, initV); //y轴加速度运动 模拟
|
||||
|
||||
final GravitySimulation spx = GravitySimulation(0, _startOffset.dx,
|
||||
_endOffset.dx, _endOffset.dx - _startOffset.dx); //x轴匀速模拟 加速度为0
|
||||
|
||||
final Animation<double> opacity = Tween<double>(begin: 1, end: 0).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation, curve: Interval(0.9, 1, curve: Curves.ease)));
|
||||
|
||||
return Positioned(
|
||||
top: spy.x(time),
|
||||
left: spx.x(time),
|
||||
child: Opacity(
|
||||
opacity: opacity.value,
|
||||
child: Container(
|
||||
height: size,
|
||||
width: size,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.all(Radius.circular(size / 2.0))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _calPoints() {
|
||||
if (_startOffset == null) {
|
||||
RenderBox stackBox = stackKey.currentContext.findRenderObject();
|
||||
Offset stackBoxOffset = stackBox.globalToLocal(Offset.zero);
|
||||
|
||||
EdgeInsets startMargin = _margin(startKey);
|
||||
RenderBox startBox = startKey.currentContext.findRenderObject();
|
||||
_startOffset = startBox.localToGlobal(Offset(
|
||||
startMargin.left + startAdjustOffset.dx,
|
||||
stackBoxOffset.dy + startMargin.top + startAdjustOffset.dy));
|
||||
|
||||
EdgeInsets endMargin = _margin(endKey);
|
||||
RenderBox endBox = endKey.currentContext.findRenderObject();
|
||||
_endOffset = endBox.localToGlobal(Offset(
|
||||
endMargin.left + endAdjustOffset.dx,
|
||||
stackBoxOffset.dy + endMargin.top + endAdjustOffset.dy));
|
||||
}
|
||||
}
|
||||
|
||||
EdgeInsets _margin(GlobalKey key) {
|
||||
final Widget widget = key.currentContext.widget;
|
||||
EdgeInsets margin = (widget is Container) ? widget.margin : EdgeInsets.zero;
|
||||
return margin ?? EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
176
lib/widgets/general/payment_verification_code_dialog.dart
Normal file
176
lib/widgets/general/payment_verification_code_dialog.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
import 'package:countdown/countdown.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinput/pin_put/pin_put.dart';
|
||||
|
||||
import '../../generated/l10n.dart';
|
||||
import '../../models/user.dart';
|
||||
import '../../utils/http_util.dart';
|
||||
import '../../utils/utils.dart';
|
||||
|
||||
typedef OnCodeVerified();
|
||||
typedef OnCancel();
|
||||
|
||||
class PaymentVerificationCodeDialog extends StatefulWidget {
|
||||
final Key key;
|
||||
final User user;
|
||||
final OnCodeVerified onCodeVerified;
|
||||
final OnCancel onCancel;
|
||||
|
||||
const PaymentVerificationCodeDialog(this.user, this.onCodeVerified, this.onCancel, {this.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return PaymentVerificationCodeDialogState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PaymentVerificationCodeDialogState extends State<PaymentVerificationCodeDialog> {
|
||||
CountDown cd = CountDown(Duration(seconds: 90));
|
||||
var countDownListener;
|
||||
bool enableGetCode = false;
|
||||
String getCodeText = '';
|
||||
String paymentCodeEncrypt = '';
|
||||
|
||||
final TextEditingController _pinPutController = TextEditingController();
|
||||
final FocusNode _pinPutFocusNode = FocusNode();
|
||||
BoxDecoration get _pinPutDecoration {
|
||||
return BoxDecoration(
|
||||
border: Border.all(color: Colors.deepPurpleAccent),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(S.of(context).payment_verification),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0),
|
||||
child: Text(
|
||||
S.of(context).payment_verification_sent(Utils.safePhoneNumber(widget.user.mobile)),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0),
|
||||
child: FlatButton(
|
||||
child: Text(getCodeText),
|
||||
onPressed: enableGetCode ? () {
|
||||
getPaymentCode();
|
||||
} : null,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0),
|
||||
child: PinPut(
|
||||
fieldsCount: 4,
|
||||
focusNode: _pinPutFocusNode,
|
||||
controller: _pinPutController,
|
||||
onSubmit: (String pin) {
|
||||
String codeString = generateSignature(pin, key: widget.user.id.toString());
|
||||
FocusScope.of(context).unfocus();
|
||||
if (paymentCodeEncrypt == codeString) {
|
||||
Navigator.of(context).pop();
|
||||
if (widget.onCodeVerified != null) {
|
||||
widget.onCodeVerified();
|
||||
}
|
||||
} else {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(S.of(context).error),
|
||||
content: Text(S.of(context).wrong_payment_verification_code),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text(S.of(context).ok),
|
||||
onPressed: () {
|
||||
_pinPutController.clear();
|
||||
_pinPutFocusNode.requestFocus();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
||||
borderRadius: BorderRadius.circular(20)),
|
||||
selectedFieldDecoration: _pinPutDecoration,
|
||||
followingFieldDecoration: _pinPutDecoration.copyWith(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(
|
||||
color: Colors.deepPurpleAccent.withOpacity(.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text(S.of(context).close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
if (widget.onCancel != null) {
|
||||
widget.onCancel();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getPaymentCode();
|
||||
}
|
||||
|
||||
void getPaymentCode() {
|
||||
HttpUtil.httpGet('v1/get-payment-verification-code',
|
||||
queryParameters: {
|
||||
'key': widget.user.id,
|
||||
'method': 'mobile', // or 'mobile'
|
||||
'is_user': false,
|
||||
},
|
||||
).then((data) {
|
||||
paymentCodeEncrypt = data['payment_code_encrypt'];
|
||||
print('data: $data');
|
||||
startCountDown();
|
||||
}).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
}
|
||||
|
||||
void startCountDown() {
|
||||
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
|
||||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
70
lib/widgets/general/popup_animation_widget.dart
Normal file
70
lib/widgets/general/popup_animation_widget.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class PopupAnimationWidget extends AnimatedWidget {
|
||||
final GlobalKey stackKey;
|
||||
final GlobalKey startKey;
|
||||
final Color color;
|
||||
final Widget child;
|
||||
final Offset popupOffset;
|
||||
final Animation<double> animation;
|
||||
|
||||
PopupAnimationWidget({
|
||||
@required this.animation,
|
||||
@required this.stackKey,
|
||||
@required this.startKey,
|
||||
@required this.child,
|
||||
this.color = Colors.yellow,
|
||||
this.popupOffset = Offset.zero,
|
||||
}) : super(listenable: animation);
|
||||
|
||||
Offset _startOffset;
|
||||
Offset _offset = Offset.zero;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_calAnimation();
|
||||
|
||||
final Animation<double> opacityAnimation = Tween<double>(begin: 0, end: 1)
|
||||
.animate(CurvedAnimation(
|
||||
parent: animation, curve: Interval(0, 0.4, curve: Curves.ease)));
|
||||
_offset =
|
||||
Offset(_startOffset.dx, _startOffset.dy - opacityAnimation.value * 80);
|
||||
|
||||
return Positioned(
|
||||
left: _offset.dx,
|
||||
top: _offset.dy,
|
||||
child: Opacity(
|
||||
opacity: opacityAnimation.value,
|
||||
child: ScaleTransition(
|
||||
alignment: Alignment.bottomCenter,
|
||||
scale: opacityAnimation,
|
||||
child: Container(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _calAnimation() {
|
||||
if (_startOffset == null) {
|
||||
final RenderBox stackBox = stackKey.currentContext.findRenderObject();
|
||||
final Offset stackBoxOffset = stackBox.globalToLocal(Offset.zero);
|
||||
|
||||
final EdgeInsets startMargin = _margin(startKey);
|
||||
final RenderBox startBox = startKey.currentContext.findRenderObject();
|
||||
|
||||
_startOffset = startBox.localToGlobal(Offset(
|
||||
startMargin.left + popupOffset.dx,
|
||||
stackBoxOffset.dy + startMargin.top + popupOffset.dy));
|
||||
}
|
||||
}
|
||||
|
||||
EdgeInsets _margin(GlobalKey key) {
|
||||
final Widget widget = key.currentContext.widget;
|
||||
final EdgeInsets margin =
|
||||
(widget is Container) ? widget.margin : EdgeInsets.zero;
|
||||
return margin ?? EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
27
lib/widgets/general/product_item.dart
Normal file
27
lib/widgets/general/product_item.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_wisetronic/models/business.dart';
|
||||
import 'package:flutter_wisetronic/models/product.dart';
|
||||
import 'package:flutter_wisetronic/widgets/desktop/desktop_product_item.dart';
|
||||
import 'package:flutter_wisetronic/widgets/mobile/mobile_product_item.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
class ProductItem extends StatelessWidget {
|
||||
final Product product;
|
||||
final Business business;
|
||||
|
||||
const ProductItem({Key key, this.product, this.business}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInformation) =>
|
||||
ScreenTypeLayout(
|
||||
mobile: MobileProductItem(product: product, business: business,),
|
||||
tablet: DesktopProductItem(product: product, business: business,),
|
||||
desktop: DesktopProductItem(product: product, business: business,),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
198
lib/widgets/general/read_more_text.dart
Normal file
198
lib/widgets/general/read_more_text.dart
Normal file
@@ -0,0 +1,198 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum TrimMode {
|
||||
Length,
|
||||
Line,
|
||||
}
|
||||
|
||||
class ReadMoreText extends StatefulWidget {
|
||||
const ReadMoreText(
|
||||
this.data, {
|
||||
Key key,
|
||||
this.trimExpandedText = ' read less',
|
||||
this.trimCollapsedText = ' ...read more',
|
||||
this.colorClickableText,
|
||||
this.trimLength = 240,
|
||||
this.trimLines = 2,
|
||||
this.trimMode = TrimMode.Length,
|
||||
this.style,
|
||||
this.textAlign,
|
||||
this.textDirection,
|
||||
this.locale,
|
||||
this.textScaleFactor,
|
||||
this.semanticsLabel,
|
||||
}) : assert(data != null),
|
||||
super(key: key);
|
||||
|
||||
final String data;
|
||||
final String trimExpandedText;
|
||||
final String trimCollapsedText;
|
||||
final Color colorClickableText;
|
||||
final int trimLength;
|
||||
final int trimLines;
|
||||
final TrimMode trimMode;
|
||||
final TextStyle style;
|
||||
final TextAlign textAlign;
|
||||
final TextDirection textDirection;
|
||||
final Locale locale;
|
||||
final double textScaleFactor;
|
||||
final String semanticsLabel;
|
||||
|
||||
@override
|
||||
ReadMoreTextState createState() => ReadMoreTextState();
|
||||
}
|
||||
|
||||
const String _kEllipsis = '\u2026';
|
||||
|
||||
const String _kLineSeparator = '\u2028';
|
||||
|
||||
class ReadMoreTextState extends State<ReadMoreText> {
|
||||
bool _readMore = true;
|
||||
|
||||
void _onTapLink() {
|
||||
setState(() => _readMore = !_readMore);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
||||
TextStyle effectiveTextStyle = widget.style;
|
||||
if (widget.style == null || widget.style.inherit) {
|
||||
effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
|
||||
}
|
||||
|
||||
final textAlign =
|
||||
widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start;
|
||||
final textDirection = widget.textDirection ?? Directionality.of(context);
|
||||
final textScaleFactor =
|
||||
widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context);
|
||||
final overflow = defaultTextStyle.overflow;
|
||||
final locale =
|
||||
widget.locale ?? Localizations.localeOf(context);
|
||||
|
||||
final colorClickableText =
|
||||
widget.colorClickableText ?? Theme.of(context).accentColor;
|
||||
|
||||
TextSpan link = TextSpan(
|
||||
text: _readMore ? widget.trimCollapsedText : widget.trimExpandedText,
|
||||
style: effectiveTextStyle.copyWith(
|
||||
color: colorClickableText,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()..onTap = _onTapLink,
|
||||
);
|
||||
|
||||
Widget result = LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
assert(constraints.hasBoundedWidth);
|
||||
final double maxWidth = constraints.maxWidth;
|
||||
|
||||
// Create a TextSpan with data
|
||||
final text = TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: widget.data,
|
||||
);
|
||||
|
||||
// Layout and measure link
|
||||
TextPainter textPainter = TextPainter(
|
||||
text: link,
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
textScaleFactor: textScaleFactor,
|
||||
maxLines: widget.trimLines,
|
||||
ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
|
||||
locale: locale,
|
||||
);
|
||||
textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth);
|
||||
final linkSize = textPainter.size;
|
||||
|
||||
// Layout and measure text
|
||||
textPainter.text = text;
|
||||
textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth);
|
||||
final textSize = textPainter.size;
|
||||
|
||||
print('linkSize $linkSize textSize $textSize');
|
||||
|
||||
// Get the endIndex of data
|
||||
bool linkLongerThanLine = false;
|
||||
int endIndex;
|
||||
|
||||
if (linkSize.width < maxWidth) {
|
||||
final pos = textPainter.getPositionForOffset(Offset(
|
||||
textSize.width - linkSize.width,
|
||||
textSize.height,
|
||||
));
|
||||
endIndex = textPainter.getOffsetBefore(pos.offset);
|
||||
}
|
||||
else {
|
||||
var pos = textPainter.getPositionForOffset(
|
||||
textSize.bottomLeft(Offset.zero),
|
||||
);
|
||||
endIndex = pos.offset;
|
||||
linkLongerThanLine = true;
|
||||
}
|
||||
|
||||
var textSpan;
|
||||
switch (widget.trimMode) {
|
||||
case TrimMode.Length:
|
||||
if (widget.trimLength < widget.data.length) {
|
||||
textSpan = TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: _readMore
|
||||
? widget.data.substring(0, widget.trimLength)
|
||||
: widget.data,
|
||||
children: <TextSpan>[link],
|
||||
);
|
||||
} else {
|
||||
textSpan = TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: widget.data,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case TrimMode.Line:
|
||||
if (textPainter.didExceedMaxLines) {
|
||||
textSpan = TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: _readMore
|
||||
? widget.data.substring(0, endIndex) +
|
||||
(linkLongerThanLine ? _kLineSeparator : '')
|
||||
: widget.data,
|
||||
children: <TextSpan>[link],
|
||||
);
|
||||
} else {
|
||||
textSpan = TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: widget.data,
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
'TrimMode type: ${widget.trimMode} is not supported');
|
||||
}
|
||||
|
||||
return RichText(
|
||||
textAlign: textAlign,
|
||||
textDirection: textDirection,
|
||||
softWrap: true,
|
||||
//softWrap,
|
||||
overflow: TextOverflow.clip,
|
||||
//overflow,
|
||||
textScaleFactor: textScaleFactor,
|
||||
text: textSpan,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (widget.semanticsLabel != null) {
|
||||
result = Semantics(
|
||||
textDirection: widget.textDirection,
|
||||
label: widget.semanticsLabel,
|
||||
child: ExcludeSemantics(
|
||||
child: result,
|
||||
),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
86
lib/widgets/general/show_price.dart
Normal file
86
lib/widgets/general/show_price.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ShowPrice extends StatelessWidget {
|
||||
final double price;
|
||||
final double regularPrice;
|
||||
final double largeFontSize;
|
||||
final double smallFontSize;
|
||||
final String currencySign;
|
||||
final Color color;
|
||||
final FontWeight fontWeight;
|
||||
|
||||
ShowPrice(this.price,
|
||||
{
|
||||
this.regularPrice,
|
||||
this.largeFontSize = 20,
|
||||
this.smallFontSize = 12,
|
||||
this.currencySign,
|
||||
this.color = Colors.red,
|
||||
this.fontWeight = FontWeight.normal,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String priceString = price.toStringAsFixed(2);
|
||||
var arr = priceString.split('.');
|
||||
String num1 = arr[0];
|
||||
String num2 = arr[1];
|
||||
|
||||
return Container(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
currencySign != null ?
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: largeFontSize / 10.0),
|
||||
child: Text(
|
||||
currencySign,
|
||||
style: TextStyle(
|
||||
fontSize: smallFontSize,
|
||||
color: color,
|
||||
fontWeight: fontWeight,
|
||||
),
|
||||
),
|
||||
) : SizedBox.shrink(),
|
||||
Container(
|
||||
child: Text(
|
||||
num1,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: largeFontSize,
|
||||
fontWeight: fontWeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: largeFontSize / 10.0),
|
||||
child: Text(
|
||||
'.$num2',
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: smallFontSize,
|
||||
fontWeight: fontWeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
regularPrice == null || price.round() >= regularPrice.round() ?
|
||||
SizedBox.shrink() :
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: largeFontSize / 2),
|
||||
child: Text(
|
||||
regularPrice.toStringAsFixed(0),
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
fontSize: smallFontSize,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
711
lib/widgets/general/sliding_up_panel.dart
Normal file
711
lib/widgets/general/sliding_up_panel.dart
Normal file
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
Name: Akshath Jain
|
||||
Date: 3/18/2019 - 4/2/2020
|
||||
Purpose: Defines the sliding_up_panel widget
|
||||
Copyright: © 2020, Akshath Jain. All rights reserved.
|
||||
Licensing: More information can be found here: https://github.com/akshathjain/sliding_up_panel/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/physics.dart';
|
||||
|
||||
enum SlideDirection{
|
||||
UP,
|
||||
DOWN,
|
||||
}
|
||||
|
||||
enum PanelState{
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
class SlidingUpPanel extends StatefulWidget {
|
||||
|
||||
/// The Widget that slides into view. When the
|
||||
/// panel is collapsed and if [collapsed] is null,
|
||||
/// then top portion of this Widget will be displayed;
|
||||
/// otherwise, [collapsed] will be displayed overtop
|
||||
/// of this Widget. If [panel] and [panelBuilder] are both non-null,
|
||||
/// [panel] will be used.
|
||||
final Widget panel;
|
||||
|
||||
/// WARNING: This feature is still in beta and is subject to change without
|
||||
/// notice. Stability is not gauranteed. Provides a [ScrollController] and
|
||||
/// [ScrollPhysics] to attach to a scrollable object in the panel that links
|
||||
/// the panel position with the scroll position. Useful for implementing an
|
||||
/// infinite scroll behavior. If [panel] and [panelBuilder] are both non-null,
|
||||
/// [panel] will be used.
|
||||
final Widget Function(ScrollController sc) panelBuilder;
|
||||
|
||||
/// The Widget displayed overtop the [panel] when collapsed.
|
||||
/// This fades out as the panel is opened.
|
||||
final Widget collapsed;
|
||||
|
||||
/// The Widget that lies underneath the sliding panel.
|
||||
/// This Widget automatically sizes itself
|
||||
/// to fill the screen.
|
||||
final Widget body;
|
||||
|
||||
/// Optional persistent widget that floats above the [panel] and attaches
|
||||
/// to the top of the [panel]. Content at the top of the panel will be covered
|
||||
/// by this widget. Add padding to the bottom of the `panel` to
|
||||
/// avoid coverage.
|
||||
final Widget header;
|
||||
|
||||
/// Optional persistent widget that floats above the [panel] and
|
||||
/// attaches to the bottom of the [panel]. Content at the bottom of the panel
|
||||
/// will be covered by this widget. Add padding to the bottom of the `panel`
|
||||
/// to avoid coverage.
|
||||
final Widget footer;
|
||||
|
||||
/// The height of the sliding panel when fully collapsed.
|
||||
final double minHeight;
|
||||
|
||||
/// The height of the sliding panel when fully open.
|
||||
final double maxHeight;
|
||||
|
||||
/// A point between [minHeight] and [maxHeight] that the panel snaps to
|
||||
/// while animating. A fast swipe on the panel will disregard this point
|
||||
/// and go directly to the open/close position. This value is represented as a
|
||||
/// percentage of the total animation distance ([maxHeight] - [minHeight]),
|
||||
/// so it must be between 0.0 and 1.0, exclusive.
|
||||
final double snapPoint;
|
||||
|
||||
/// A border to draw around the sliding panel sheet.
|
||||
final Border border;
|
||||
|
||||
/// If non-null, the corners of the sliding panel sheet are rounded by this [BorderRadiusGeometry].
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
|
||||
/// A list of shadows cast behind the sliding panel sheet.
|
||||
final List<BoxShadow> boxShadow;
|
||||
|
||||
/// The color to fill the background of the sliding panel sheet.
|
||||
final Color color;
|
||||
|
||||
/// The amount to inset the children of the sliding panel sheet.
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
/// Empty space surrounding the sliding panel sheet.
|
||||
final EdgeInsetsGeometry margin;
|
||||
|
||||
/// Set to false to not to render the sheet the [panel] sits upon.
|
||||
/// This means that only the [body], [collapsed], and the [panel]
|
||||
/// Widgets will be rendered.
|
||||
/// Set this to false if you want to achieve a floating effect or
|
||||
/// want more customization over how the sliding panel
|
||||
/// looks like.
|
||||
final bool renderPanelSheet;
|
||||
|
||||
/// Set to false to disable the panel from snapping open or closed.
|
||||
final bool panelSnapping;
|
||||
|
||||
/// If non-null, this can be used to control the state of the panel.
|
||||
final PanelController controller;
|
||||
|
||||
/// If non-null, shows a darkening shadow over the [body] as the panel slides open.
|
||||
final bool backdropEnabled;
|
||||
|
||||
/// Shows a darkening shadow of this [Color] over the [body] as the panel slides open.
|
||||
final Color backdropColor;
|
||||
|
||||
/// The opacity of the backdrop when the panel is fully open.
|
||||
/// This value can range from 0.0 to 1.0 where 0.0 is completely transparent
|
||||
/// and 1.0 is completely opaque.
|
||||
final double backdropOpacity;
|
||||
|
||||
/// Flag that indicates whether or not tapping the
|
||||
/// backdrop closes the panel. Defaults to true.
|
||||
final bool backdropTapClosesPanel;
|
||||
|
||||
/// If non-null, this callback
|
||||
/// is called as the panel slides around with the
|
||||
/// current position of the panel. The position is a double
|
||||
/// between 0.0 and 1.0 where 0.0 is fully collapsed and 1.0 is fully open.
|
||||
final void Function(double position) onPanelSlide;
|
||||
|
||||
/// If non-null, this callback is called when the
|
||||
/// panel is fully opened
|
||||
final VoidCallback onPanelOpened;
|
||||
|
||||
/// If non-null, this callback is called when the panel
|
||||
/// is fully collapsed.
|
||||
final VoidCallback onPanelClosed;
|
||||
|
||||
/// If non-null and true, the SlidingUpPanel exhibits a
|
||||
/// parallax effect as the panel slides up. Essentially,
|
||||
/// the body slides up as the panel slides up.
|
||||
final bool parallaxEnabled;
|
||||
|
||||
/// Allows for specifying the extent of the parallax effect in terms
|
||||
/// of the percentage the panel has slid up/down. Recommended values are
|
||||
/// within 0.0 and 1.0 where 0.0 is no parallax and 1.0 mimics a
|
||||
/// one-to-one scrolling effect. Defaults to a 10% parallax.
|
||||
final double parallaxOffset;
|
||||
|
||||
/// Allows toggling of the draggability of the SlidingUpPanel.
|
||||
/// Set this to false to prevent the user from being able to drag
|
||||
/// the panel up and down. Defaults to true.
|
||||
final bool isDraggable;
|
||||
|
||||
/// Either SlideDirection.UP or SlideDirection.DOWN. Indicates which way
|
||||
/// the panel should slide. Defaults to UP. If set to DOWN, the panel attaches
|
||||
/// itself to the top of the screen and is fully opened when the user swipes
|
||||
/// down on the panel.
|
||||
final SlideDirection slideDirection;
|
||||
|
||||
/// The default state of the panel; either PanelState.OPEN or PanelState.CLOSED.
|
||||
/// This value defaults to PanelState.CLOSED which indicates that the panel is
|
||||
/// in the closed position and must be opened. PanelState.OPEN indicates that
|
||||
/// by default the Panel is open and must be swiped closed by the user.
|
||||
final PanelState defaultPanelState;
|
||||
|
||||
SlidingUpPanel({
|
||||
Key key,
|
||||
this.panel,
|
||||
this.panelBuilder,
|
||||
this.body,
|
||||
this.collapsed,
|
||||
this.minHeight = 100.0,
|
||||
this.maxHeight = 500.0,
|
||||
this.snapPoint,
|
||||
this.border,
|
||||
this.borderRadius,
|
||||
this.boxShadow = const <BoxShadow>[
|
||||
BoxShadow(
|
||||
blurRadius: 8.0,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.25),
|
||||
)
|
||||
],
|
||||
this.color = Colors.white,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.renderPanelSheet = true,
|
||||
this.panelSnapping = true,
|
||||
this.controller,
|
||||
this.backdropEnabled = false,
|
||||
this.backdropColor = Colors.black,
|
||||
this.backdropOpacity = 0.5,
|
||||
this.backdropTapClosesPanel = true,
|
||||
this.onPanelSlide,
|
||||
this.onPanelOpened,
|
||||
this.onPanelClosed,
|
||||
this.parallaxEnabled = false,
|
||||
this.parallaxOffset = 0.1,
|
||||
this.isDraggable = true,
|
||||
this.slideDirection = SlideDirection.UP,
|
||||
this.defaultPanelState = PanelState.CLOSED,
|
||||
this.header,
|
||||
this.footer
|
||||
}) : assert(panel != null || panelBuilder != null),
|
||||
assert(0 <= backdropOpacity && backdropOpacity <= 1.0),
|
||||
assert (snapPoint == null || 0 < snapPoint && snapPoint < 1.0),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
_SlidingUpPanelState createState() => _SlidingUpPanelState();
|
||||
}
|
||||
|
||||
class _SlidingUpPanelState extends State<SlidingUpPanel> with SingleTickerProviderStateMixin{
|
||||
|
||||
AnimationController _ac;
|
||||
|
||||
ScrollController _sc;
|
||||
bool _scrollingEnabled = false;
|
||||
VelocityTracker _vt = new VelocityTracker();
|
||||
|
||||
bool _isPanelVisible = true;
|
||||
|
||||
@override
|
||||
void initState(){
|
||||
super.initState();
|
||||
|
||||
_ac = new AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
value: widget.defaultPanelState == PanelState.CLOSED ? 0.0 : 1.0 //set the default panel state (i.e. set initial value of _ac)
|
||||
)..addListener((){
|
||||
if(widget.onPanelSlide != null) widget.onPanelSlide(_ac.value);
|
||||
|
||||
if(widget.onPanelOpened != null && _ac.value == 1.0) widget.onPanelOpened();
|
||||
|
||||
if(widget.onPanelClosed != null && _ac.value == 0.0) widget.onPanelClosed();
|
||||
});
|
||||
|
||||
// prevent the panel content from being scrolled only if the widget is
|
||||
// draggable and panel scrolling is enabled
|
||||
_sc = new ScrollController();
|
||||
_sc.addListener((){
|
||||
if(widget.isDraggable && !_scrollingEnabled)
|
||||
_sc.jumpTo(0);
|
||||
});
|
||||
|
||||
widget.controller?._addState(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: widget.slideDirection == SlideDirection.UP ? Alignment.bottomCenter : Alignment.topCenter,
|
||||
children: <Widget>[
|
||||
|
||||
//make the back widget take up the entire back side
|
||||
widget.body != null ? AnimatedBuilder(
|
||||
animation: _ac,
|
||||
builder: (context, child){
|
||||
return Positioned(
|
||||
top: widget.parallaxEnabled ? _getParallax() : 0.0,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: widget.body,
|
||||
),
|
||||
) : Container(),
|
||||
|
||||
|
||||
//the backdrop to overlay on the body
|
||||
!widget.backdropEnabled ? Container() : GestureDetector(
|
||||
onVerticalDragEnd: widget.backdropTapClosesPanel ? (DragEndDetails dets){
|
||||
// only trigger a close if the drag is towards panel close position
|
||||
if((widget.slideDirection == SlideDirection.UP ? 1 : -1) * dets.velocity.pixelsPerSecond.dy > 0)
|
||||
_close();
|
||||
} : null,
|
||||
onTap: widget.backdropTapClosesPanel ? () => _close() : null,
|
||||
child: AnimatedBuilder(
|
||||
animation: _ac,
|
||||
builder: (context, _) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
|
||||
//set color to null so that touch events pass through
|
||||
//to the body when the panel is closed, otherwise,
|
||||
//if a color exists, then touch events won't go through
|
||||
color: _ac.value == 0.0 ? null : widget.backdropColor.withOpacity(widget.backdropOpacity * _ac.value),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
|
||||
//the actual sliding part
|
||||
!_isPanelVisible ? Container() : _gestureHandler(
|
||||
child: AnimatedBuilder(
|
||||
animation: _ac,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
height: _ac.value * (widget.maxHeight - widget.minHeight) + widget.minHeight,
|
||||
margin: widget.margin,
|
||||
padding: widget.padding,
|
||||
decoration: widget.renderPanelSheet ? BoxDecoration(
|
||||
border: widget.border,
|
||||
borderRadius: widget.borderRadius,
|
||||
boxShadow: widget.boxShadow,
|
||||
color: widget.color,
|
||||
) : null,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
overflow: Overflow.visible,
|
||||
children: <Widget>[
|
||||
|
||||
//open panel
|
||||
Positioned(
|
||||
top: widget.slideDirection == SlideDirection.UP ? 0.0 : null,
|
||||
bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null,
|
||||
width: MediaQuery.of(context).size.width -
|
||||
(widget.margin != null ? widget.margin.horizontal : 0) -
|
||||
(widget.padding != null ? widget.padding.horizontal : 0),
|
||||
child: Container(
|
||||
height: widget.maxHeight,
|
||||
child: widget.panel != null
|
||||
? widget.panel
|
||||
: widget.panelBuilder(_sc),
|
||||
),
|
||||
),
|
||||
|
||||
// header
|
||||
widget.header != null ? Positioned(
|
||||
top: widget.slideDirection == SlideDirection.UP ? 0.0 : null,
|
||||
bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null,
|
||||
child: widget.header,
|
||||
) : Container(),
|
||||
|
||||
// footer
|
||||
widget.footer != null ? Positioned(
|
||||
top: widget.slideDirection == SlideDirection.UP ? null : 0.0,
|
||||
bottom: widget.slideDirection == SlideDirection.DOWN ? null : 0.0,
|
||||
child: widget.footer
|
||||
) : Container(),
|
||||
|
||||
// collapsed panel
|
||||
Positioned(
|
||||
top: widget.slideDirection == SlideDirection.UP ? 0.0 : null,
|
||||
bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null,
|
||||
width: MediaQuery.of(context).size.width -
|
||||
(widget.margin != null ? widget.margin.horizontal : 0) -
|
||||
(widget.padding != null ? widget.padding.horizontal : 0),
|
||||
child: Container(
|
||||
height: widget.minHeight,
|
||||
child: widget.collapsed == null ? Container() : FadeTransition(
|
||||
opacity: Tween(begin: 1.0, end: 0.0).animate(_ac),
|
||||
|
||||
// if the panel is open ignore pointers (touch events) on the collapsed
|
||||
// child so that way touch events go through to whatever is underneath
|
||||
child: IgnorePointer(
|
||||
ignoring: _isPanelOpen,
|
||||
child: widget.collapsed
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose(){
|
||||
_ac.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double _getParallax(){
|
||||
if(widget.slideDirection == SlideDirection.UP)
|
||||
return -_ac.value * (widget.maxHeight - widget.minHeight) * widget.parallaxOffset;
|
||||
else
|
||||
return _ac.value * (widget.maxHeight - widget.minHeight) * widget.parallaxOffset;
|
||||
}
|
||||
|
||||
// returns a gesture detector if panel is used
|
||||
// and a listener if panelBuilder is used.
|
||||
// this is because the listener is designed only for use with linking the scrolling of
|
||||
// panels and using it for panels that don't want to linked scrolling yields odd results
|
||||
Widget _gestureHandler({Widget child}){
|
||||
if (!widget.isDraggable) return child;
|
||||
|
||||
if (widget.panel != null){
|
||||
return GestureDetector(
|
||||
onVerticalDragUpdate: (DragUpdateDetails dets) => _onGestureSlide(dets.delta.dy),
|
||||
onVerticalDragEnd: (DragEndDetails dets) => _onGestureEnd(dets.velocity),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return Listener(
|
||||
onPointerDown: (PointerDownEvent p) => _vt.addPosition(p.timeStamp, p.position),
|
||||
onPointerMove: (PointerMoveEvent p){
|
||||
_vt.addPosition(p.timeStamp, p.position); // add current position for velocity tracking
|
||||
_onGestureSlide(p.delta.dy);
|
||||
},
|
||||
onPointerUp: (PointerUpEvent p) => _onGestureEnd(_vt.getVelocity()),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
// handles the sliding gesture
|
||||
void _onGestureSlide(double dy){
|
||||
|
||||
// only slide the panel if scrolling is not enabled
|
||||
if(!_scrollingEnabled){
|
||||
if(widget.slideDirection == SlideDirection.UP)
|
||||
_ac.value -= dy / (widget.maxHeight - widget.minHeight);
|
||||
else
|
||||
_ac.value += dy / (widget.maxHeight - widget.minHeight);
|
||||
}
|
||||
|
||||
// if the panel is open and the user hasn't scrolled, we need to determine
|
||||
// whether to enable scrolling if the user swipes up, or disable closing and
|
||||
// begin to close the panel if the user swipes down
|
||||
if(_isPanelOpen && _sc.hasClients && _sc.offset <= 0){
|
||||
setState(() {
|
||||
if(dy < 0){
|
||||
_scrollingEnabled = true;
|
||||
}else{
|
||||
_scrollingEnabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// handles when user stops sliding
|
||||
void _onGestureEnd(Velocity v){
|
||||
double minFlingVelocity = 365.0;
|
||||
double kSnap = 8;
|
||||
|
||||
//let the current animation finish before starting a new one
|
||||
if(_ac.isAnimating) return;
|
||||
|
||||
// if scrolling is allowed and the panel is open, we don't want to close
|
||||
// the panel if they swipe up on the scrollable
|
||||
if(_isPanelOpen && _scrollingEnabled) return;
|
||||
|
||||
//check if the velocity is sufficient to constitute fling to end
|
||||
double visualVelocity = -v.pixelsPerSecond.dy / (widget.maxHeight - widget.minHeight);
|
||||
|
||||
// reverse visual velocity to account for slide direction
|
||||
if(widget.slideDirection == SlideDirection.DOWN)
|
||||
visualVelocity = -visualVelocity;
|
||||
|
||||
|
||||
// get minimum distances to figure out where the panel is at
|
||||
double d2Close = _ac.value;
|
||||
double d2Open = 1 - _ac.value;
|
||||
double d2Snap = ((widget.snapPoint ?? 3) -_ac.value).abs(); // large value if null results in not every being the min
|
||||
double minDistance = min(d2Close, min(d2Snap, d2Open));
|
||||
|
||||
// check if velocity is sufficient for a fling
|
||||
if(v.pixelsPerSecond.dy.abs() >= minFlingVelocity){
|
||||
|
||||
// snapPoint exists
|
||||
if(widget.panelSnapping && widget.snapPoint != null){
|
||||
if(v.pixelsPerSecond.dy.abs() >= kSnap*minFlingVelocity || minDistance == d2Snap)
|
||||
_ac.fling(velocity: visualVelocity);
|
||||
else
|
||||
_flingPanelToPosition(widget.snapPoint, visualVelocity);
|
||||
|
||||
// no snap point exists
|
||||
}else if(widget.panelSnapping){
|
||||
_ac.fling(velocity: visualVelocity);
|
||||
|
||||
// panel snapping disabled
|
||||
}else{
|
||||
_ac.animateTo(
|
||||
_ac.value + visualVelocity * 0.16,
|
||||
duration: Duration(milliseconds: 410),
|
||||
curve: Curves.decelerate,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the controller is already halfway there
|
||||
if (widget.panelSnapping) {
|
||||
|
||||
if(minDistance == d2Close){
|
||||
_close();
|
||||
}else if(minDistance == d2Snap){
|
||||
_flingPanelToPosition(widget.snapPoint, visualVelocity);
|
||||
}else{
|
||||
_open();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _flingPanelToPosition(double targetPos, double velocity){
|
||||
final Simulation simulation = SpringSimulation(
|
||||
SpringDescription.withDampingRatio(
|
||||
mass: 1.0,
|
||||
stiffness: 500.0,
|
||||
ratio: 1.0,
|
||||
),
|
||||
_ac.value,
|
||||
targetPos,
|
||||
velocity
|
||||
);
|
||||
|
||||
_ac.animateWith(simulation);
|
||||
}
|
||||
|
||||
//---------------------------------
|
||||
//PanelController related functions
|
||||
//---------------------------------
|
||||
|
||||
//close the panel
|
||||
Future<void> _close(){
|
||||
return _ac.fling(velocity: -1.0);
|
||||
}
|
||||
|
||||
//open the panel
|
||||
Future<void> _open(){
|
||||
return _ac.fling(velocity: 1.0);
|
||||
}
|
||||
|
||||
//hide the panel (completely offscreen)
|
||||
Future<void> _hide(){
|
||||
return _ac.fling(velocity: -1.0).then((x){
|
||||
setState(() {
|
||||
_isPanelVisible = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//show the panel (in collapsed mode)
|
||||
Future<void> _show(){
|
||||
return _ac.fling(velocity: -1.0).then((x){
|
||||
setState(() {
|
||||
_isPanelVisible = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//animate the panel position to value - must
|
||||
//be between 0.0 and 1.0
|
||||
Future<void> _animatePanelToPosition(double value, {Duration duration, Curve curve = Curves.linear}){
|
||||
assert(0.0 <= value && value <= 1.0);
|
||||
return _ac.animateTo(value, duration: duration, curve: curve);
|
||||
}
|
||||
|
||||
//animate the panel position to the snap point
|
||||
//REQUIRES that widget.snapPoint != null
|
||||
Future<void> _animatePanelToSnapPoint({Duration duration, Curve curve = Curves.linear}){
|
||||
assert(widget.snapPoint != null);
|
||||
return _ac.animateTo(widget.snapPoint, duration: duration, curve: curve);
|
||||
}
|
||||
|
||||
//set the panel position to value - must
|
||||
//be between 0.0 and 1.0
|
||||
set _panelPosition(double value){
|
||||
assert(0.0 <= value && value <= 1.0);
|
||||
_ac.value = value;
|
||||
}
|
||||
|
||||
//get the current panel position
|
||||
//returns the % offset from collapsed state
|
||||
//as a decimal between 0.0 and 1.0
|
||||
double get _panelPosition => _ac.value;
|
||||
|
||||
//returns whether or not
|
||||
//the panel is still animating
|
||||
bool get _isPanelAnimating => _ac.isAnimating;
|
||||
|
||||
//returns whether or not the
|
||||
//panel is open
|
||||
bool get _isPanelOpen => _ac.value == 1.0;
|
||||
|
||||
//returns whether or not the
|
||||
//panel is closed
|
||||
bool get _isPanelClosed => _ac.value == 0.0;
|
||||
|
||||
//returns whether or not the
|
||||
//panel is shown/hidden
|
||||
bool get _isPanelShown => _isPanelVisible;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class PanelController{
|
||||
_SlidingUpPanelState _panelState;
|
||||
|
||||
void _addState(_SlidingUpPanelState panelState){
|
||||
this._panelState = panelState;
|
||||
}
|
||||
|
||||
/// Determine if the panelController is attached to an instance
|
||||
/// of the SlidingUpPanel (this property must return true before any other
|
||||
/// functions can be used)
|
||||
bool get isAttached => _panelState != null;
|
||||
|
||||
/// Closes the sliding panel to its collapsed state (i.e. to the minHeight)
|
||||
Future<void> close(){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._close();
|
||||
}
|
||||
|
||||
/// Opens the sliding panel fully
|
||||
/// (i.e. to the maxHeight)
|
||||
Future<void> open(){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._open();
|
||||
}
|
||||
|
||||
/// Hides the sliding panel (i.e. is invisible)
|
||||
Future<void> hide(){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._hide();
|
||||
}
|
||||
|
||||
/// Shows the sliding panel in its collapsed state
|
||||
/// (i.e. "un-hide" the sliding panel)
|
||||
Future<void> show(){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._show();
|
||||
}
|
||||
|
||||
/// Animates the panel position to the value.
|
||||
/// The value must between 0.0 and 1.0
|
||||
/// where 0.0 is fully collapsed and 1.0 is completely open.
|
||||
/// (optional) duration specifies the time for the animation to complete
|
||||
/// (optional) curve specifies the easing behavior of the animation.
|
||||
Future<void> animatePanelToPosition(double value, {Duration duration, Curve curve = Curves.linear}){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
assert(0.0 <= value && value <= 1.0);
|
||||
return _panelState._animatePanelToPosition(value, duration: duration, curve: curve);
|
||||
}
|
||||
|
||||
/// Animates the panel position to the snap point
|
||||
/// Requires that the SlidingUpPanel snapPoint property is not null
|
||||
/// (optional) duration specifies the time for the animation to complete
|
||||
/// (optional) curve specifies the easing behavior of the animation.
|
||||
Future<void> animatePanelToSnapPoint({Duration duration, Curve curve = Curves.linear}){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
assert(_panelState.widget.snapPoint != null, "SlidingUpPanel snapPoint property must not be null");
|
||||
return _panelState._animatePanelToSnapPoint(duration: duration, curve: curve);
|
||||
}
|
||||
|
||||
/// Sets the panel position (without animation).
|
||||
/// The value must between 0.0 and 1.0
|
||||
/// where 0.0 is fully collapsed and 1.0 is completely open.
|
||||
set panelPosition(double value){
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
assert(0.0 <= value && value <= 1.0);
|
||||
_panelState._panelPosition = value;
|
||||
}
|
||||
|
||||
/// Gets the current panel position.
|
||||
/// Returns the % offset from collapsed state
|
||||
/// to the open state
|
||||
/// as a decimal between 0.0 and 1.0
|
||||
/// where 0.0 is fully collapsed and
|
||||
/// 1.0 is full open.
|
||||
double get panelPosition{
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._panelPosition;
|
||||
}
|
||||
|
||||
/// Returns whether or not the panel is
|
||||
/// currently animating.
|
||||
bool get isPanelAnimating{
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._isPanelAnimating;
|
||||
}
|
||||
|
||||
/// Returns whether or not the
|
||||
/// panel is open.
|
||||
bool get isPanelOpen{
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._isPanelOpen;
|
||||
}
|
||||
|
||||
/// Returns whether or not the
|
||||
/// panel is closed.
|
||||
bool get isPanelClosed{
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._isPanelClosed;
|
||||
}
|
||||
|
||||
/// Returns whether or not the
|
||||
/// panel is shown/hidden.
|
||||
bool get isPanelShown{
|
||||
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
|
||||
return _panelState._isPanelShown;
|
||||
}
|
||||
|
||||
}
|
||||
158
lib/widgets/general/stripe_pay.dart
Normal file
158
lib/widgets/general/stripe_pay.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stripe_payment/stripe_payment.dart';
|
||||
|
||||
import '../../constants.dart';
|
||||
import '../../events/eventbus.dart';
|
||||
import '../../events/events.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 StripePay extends StatefulWidget {
|
||||
final Key key;
|
||||
final Order order;
|
||||
final PaymentPlatform paymentPlatform;
|
||||
final StripePaymentMethod stripePaymentMethod;
|
||||
const StripePay(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return StripePayState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StripePayState extends State<StripePay> {
|
||||
|
||||
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
|
||||
|
||||
bool isSubmitting;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
store.dispatch(UpdateContext(context));
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
if (widget.stripePaymentMethod != null) {
|
||||
_paymentWithPaymentMethod(context);
|
||||
} else {
|
||||
_paymentRequestWithCardForm(context);
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: Center(
|
||||
child: Icon(
|
||||
Icons.credit_card,
|
||||
size: 40.0,
|
||||
color: Colors.black26,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isSubmitting = false;
|
||||
StripePayment.setOptions(
|
||||
StripeOptions(publishableKey: widget.paymentPlatform.publishableKey,
|
||||
merchantId: widget.paymentPlatform.merchantId,
|
||||
androidPayMode: 'test')
|
||||
);
|
||||
}
|
||||
|
||||
_paymentWithPaymentMethod(BuildContext context) async {
|
||||
Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId,
|
||||
widget.stripePaymentMethod.paymentMethodId,
|
||||
widget.stripePaymentMethod.paymentMethodType, (response){
|
||||
|
||||
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
|
||||
StripePayment.confirmPaymentIntent(
|
||||
PaymentIntent(
|
||||
clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET],
|
||||
paymentMethodId: response.data['payment_method'],
|
||||
),
|
||||
).then((paymentIntentResult) {
|
||||
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
|
||||
Utils.stripeChargedSuccess(widget.order,
|
||||
widget.stripePaymentMethod.paymentMethodId,
|
||||
paymentIntentResult.paymentIntentId,
|
||||
(response) {
|
||||
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);
|
||||
}
|
||||
|
||||
_paymentRequestWithCardForm(BuildContext context) async {
|
||||
StripePayment.paymentRequestWithCardForm(
|
||||
CardFormPaymentRequest()
|
||||
).then((paymentMethod) {
|
||||
Utils.stripePaymentIntent(widget.order, null, paymentMethod.id, paymentMethod.type, (response){
|
||||
|
||||
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
|
||||
StripePayment.confirmPaymentIntent(
|
||||
PaymentIntent(
|
||||
clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET],
|
||||
paymentMethodId: response.data['payment_method'],
|
||||
),
|
||||
).then((paymentIntentResult) {
|
||||
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
|
||||
Utils.stripeChargedSuccess(widget.order,
|
||||
paymentMethod.id,
|
||||
paymentIntentResult.paymentIntentId,
|
||||
(response) {
|
||||
eventBus.fire(OnOrderUpdated());
|
||||
Routes.router.navigateTo(context, '/orderdetail/${widget
|
||||
.order.id}', replace: true);
|
||||
},
|
||||
(showErrorDialog)
|
||||
);
|
||||
} else {
|
||||
showErrorDialog(Exception('Unknown error'));
|
||||
}
|
||||
}).catchError(showErrorDialog);
|
||||
}
|
||||
}, (showErrorDialog),
|
||||
cardBrand: paymentMethod.card.brand,
|
||||
cardCountry: paymentMethod.card.country,
|
||||
cardExpMonth: paymentMethod.card.expMonth,
|
||||
cardExpYear: paymentMethod.card.expYear,
|
||||
cardFunding: paymentMethod.card.funding,
|
||||
cardLast4: paymentMethod.card.last4,
|
||||
);
|
||||
}).catchError(showErrorDialog);
|
||||
isSubmitting = true;
|
||||
Utils.showSubmitDialog(context);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
20
lib/widgets/general/style.dart
Normal file
20
lib/widgets/general/style.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Style {
|
||||
static final fontSize = 15.0;
|
||||
static final primaryColor = const Color(0xFF3190e8);
|
||||
static final backgroundColor = const Color(0xFFFFFFFF);
|
||||
static final emptyBackgroundColor = const Color(0xFFF5F5F5);
|
||||
static final borderColor = const Color(0xFFE4E4E4);
|
||||
static final gPadding = 16.0;
|
||||
static final textStyle = new TextStyle(
|
||||
color: const Color(0xFF333333),
|
||||
fontSize: fontSize,
|
||||
);
|
||||
|
||||
static BoxDecoration testDecoration(Color color) {
|
||||
return new BoxDecoration(
|
||||
border: new Border.all(color: color),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_wisetronic/utils/utils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../routes.dart';
|
||||
@@ -21,6 +22,8 @@ class TextLink extends StatelessWidget {
|
||||
final bool rootNavigator;
|
||||
final TransitionType transition;
|
||||
final bool closeDrawer;
|
||||
final bool isEmail;
|
||||
final bool isPhone;
|
||||
TextLink(this.title, this.url, {
|
||||
this.color,
|
||||
this.paddingHorizontal,
|
||||
@@ -34,13 +37,17 @@ class TextLink extends StatelessWidget {
|
||||
bool rootNavigator,
|
||||
this.transition,
|
||||
bool closeDrawer,
|
||||
bool isEmail,
|
||||
bool isPhone,
|
||||
}) :
|
||||
isLink = isLink ?? false,
|
||||
replace = replace ?? false,
|
||||
clearStack = clearStack ?? false,
|
||||
maintainState = maintainState ?? true,
|
||||
rootNavigator = rootNavigator ?? false,
|
||||
closeDrawer = closeDrawer ?? false;
|
||||
closeDrawer = closeDrawer ?? false,
|
||||
isEmail = isEmail ?? false,
|
||||
isPhone = isPhone ?? false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -69,22 +76,28 @@ class TextLink extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
if (!isLink) {
|
||||
if (closeDrawer) {
|
||||
Routes.router.pop(context);
|
||||
}
|
||||
Routes.router.navigateTo(
|
||||
context, url,
|
||||
replace: replace,
|
||||
clearStack: clearStack,
|
||||
maintainState: maintainState,
|
||||
rootNavigator: rootNavigator,
|
||||
);
|
||||
} else {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
if (selected == null || !selected) {
|
||||
if (isEmail) {
|
||||
Utils.openEmail(url);
|
||||
} else if (isPhone) {
|
||||
Utils.callPhone(url);
|
||||
} else if (!isLink) {
|
||||
if (closeDrawer) {
|
||||
Routes.router.pop(context);
|
||||
}
|
||||
Routes.router.navigateTo(
|
||||
context, url,
|
||||
replace: replace,
|
||||
clearStack: clearStack,
|
||||
maintainState: maintainState,
|
||||
rootNavigator: rootNavigator,
|
||||
);
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user