backup. before shop update

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

View File

@@ -0,0 +1,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;
}
}

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

View File

@@ -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() {

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

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

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

View File

@@ -46,7 +46,7 @@ class DesktopIndexMainContent1State extends State<DesktopIndexMainContent1> {
),
),
decoration: BoxDecoration(
color: Colors.lightGreen,
color: Color(0xFF4FB0C6),
borderRadius: BorderRadius.circular(10),
),
),

View File

@@ -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,
),
),
);

View File

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

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

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

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

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

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

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

View File

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

View 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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@@ -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),
),

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

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

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

View File

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

View File

@@ -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),

View File

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

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

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

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

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

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

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

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

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

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

View File

@@ -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';
}
}
}
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More