2057 lines
68 KiB
Dart
2057 lines
68 KiB
Dart
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
import 'package:badges/badges.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
|
import 'package:flutter_wisetronic/widgets/general/show_price.dart';
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
|
import 'package:pull_to_refresh/pull_to_refresh.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/business.dart';
|
|
import '../../models/cart_info.dart';
|
|
import '../../models/cart_line_item.dart';
|
|
import '../../models/category_products.dart';
|
|
import '../../models/comment.dart';
|
|
import '../../models/product.dart';
|
|
import '../../models/product_image.dart';
|
|
import '../../pages/product_detail_page.dart';
|
|
import '../../pages/store_product_search.dart';
|
|
import '../../routes.dart';
|
|
import '../../store/actions.dart';
|
|
import '../../store/store.dart';
|
|
import '../../utils/http_util.dart';
|
|
import '../../utils/shop_scroll_controller.dart';
|
|
import '../../utils/shop_scroll_coordinator.dart';
|
|
import '../../utils/util_web.dart'
|
|
if (dart.library.io) '../../utils/util_io.dart';
|
|
import '../../utils/utils.dart';
|
|
import '../../widgets/general/add_remove_button.dart';
|
|
import '../../widgets/general/animation_point_manager.dart';
|
|
import '../../widgets/general/carousel.dart';
|
|
import '../../widgets/general/product_item.dart';
|
|
import '../../widgets/general/read_more_text.dart';
|
|
import '../../widgets/general/sliding_up_panel.dart';
|
|
import '../../widgets/general/style.dart';
|
|
import 'shopping_cart_bar.dart';
|
|
|
|
class MobileShop extends StatefulWidget {
|
|
final int businessId;
|
|
|
|
const MobileShop({Key key, this.businessId}) : super(key: key);
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => MobileShopState();
|
|
}
|
|
|
|
MediaQueryData mediaQuery;
|
|
double statusBarHeight;
|
|
double screenWidth;
|
|
double screenHeight;
|
|
|
|
class MobileShopState extends State<MobileShop>
|
|
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
|
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
|
|
|
Business _business;
|
|
List<CategoryProducts> _categoryProducts;
|
|
List<Product> _featuredProducts;
|
|
List<Product> _hotSaleProducts;
|
|
|
|
List<dynamic> _prompts = [];
|
|
bool checkCloseFlag = false;
|
|
|
|
bool displayProductByCategoryClick = false;
|
|
String displayProductByCategoryClickIndicator = '';
|
|
int categoryId = 0;
|
|
int _productCurrentPage = 1;
|
|
|
|
static const num _categoryHeight = 50.0;
|
|
static const num _categoryDescHeight = 50.0;
|
|
static const num _productHeight = 115.0;
|
|
|
|
int _categoryIndex = 0;
|
|
bool _categoryIndexChange = false;
|
|
bool _isLoading = false;
|
|
|
|
bool refresh = false;
|
|
|
|
ShopScrollCoordinator _shopCoordinator;
|
|
ShopScrollController _pageScrollController;
|
|
TabController _tabController;
|
|
final double _sliverAppBarInitHeight = 150.0;
|
|
final double _tabBarHeight = 50.0;
|
|
double _sliverAppBarMaxHeight;
|
|
|
|
ShopScrollController _listScrollController1;
|
|
ShopScrollController _listScrollController2;
|
|
ShopScrollController _listScrollController3;
|
|
|
|
AnimationPointManager _animationPointManager = AnimationPointManager();
|
|
GlobalKey stackKey = GlobalKey();
|
|
GlobalKey endKey = GlobalKey();
|
|
|
|
List<Comment> comments;
|
|
int _commentPage = 1;
|
|
int _commentPageCount = 1;
|
|
bool _commentLoadingFinish = false;
|
|
RefreshController _commentRefreshController =
|
|
RefreshController(initialRefresh: true);
|
|
|
|
PanelController panelController = PanelController();
|
|
SlidingUpPanel _slidUpShoppingCart;
|
|
|
|
SliverPersistentHeader promotHeader;
|
|
|
|
bool _animationFinish = true;
|
|
|
|
Carousel slidingGellery;
|
|
|
|
// StreamSubscription onProductWillAddToCartSubscription;
|
|
// StreamSubscription onProductWillRemoveFromCartSubscription;
|
|
|
|
void _onCommentRefresh() {
|
|
if (_commentPage - 1 > 1) {
|
|
_commentPage -= 1;
|
|
} else {
|
|
_commentPage = 1;
|
|
}
|
|
_commentRefreshController.resetNoData();
|
|
_loadComment(true);
|
|
}
|
|
|
|
void _onCommentLoadMore() {
|
|
if (_commentPageCount > _commentPage) {
|
|
_commentPage += 1;
|
|
_loadComment(false);
|
|
} else {
|
|
_commentRefreshController.loadNoData();
|
|
}
|
|
}
|
|
|
|
_loadComment(bool isRefresh) {
|
|
_commentLoadingFinish = false;
|
|
HttpUtil.httpGet(
|
|
'v1/get-comments',
|
|
queryParameters: {
|
|
'page': _commentPage.toString(),
|
|
'size': Constants.ORDERS_PER_PAGE.toString(),
|
|
},
|
|
businessId: _business.id,
|
|
).then((data) {
|
|
if (isRefresh) {
|
|
_commentRefreshController.refreshCompleted();
|
|
} else {
|
|
_commentRefreshController.loadComplete();
|
|
}
|
|
_commentPage = int.parse(data['_meta']['currentPage'].toString());
|
|
_commentPageCount = int.parse(data['_meta']['pageCount'].toString());
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
comments =
|
|
(data['items'] as List).map((e) => Comment.fromJson(e)).toList();
|
|
});
|
|
}
|
|
}).catchError((error) {
|
|
Utils.showMessageDialog(context, error);
|
|
});
|
|
}
|
|
|
|
itemAddToCart(GlobalKey startKey) {
|
|
if (_animationFinish == false) {
|
|
return;
|
|
}
|
|
_animationFinish = false;
|
|
|
|
print("start key " + startKey.toString());
|
|
print("stack key " + stackKey.toString());
|
|
print("end key " + endKey.toString());
|
|
|
|
_animationPointManager.addParabolicAniamtion(
|
|
vsync: this,
|
|
stackKey: stackKey,
|
|
startKey: startKey,
|
|
endKey: endKey,
|
|
color: Colors.red,
|
|
statusListener: (AnimationStatus status) {
|
|
if (mounted) {
|
|
if (status == AnimationStatus.completed) {
|
|
// buyOnTap();
|
|
_animationFinish = true;
|
|
setState(() {});
|
|
}
|
|
}
|
|
},
|
|
duration: Duration(milliseconds: 800),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_business == null) {
|
|
return new Scaffold(
|
|
body: Center(
|
|
child: SpinKitWave(
|
|
color: Colors.lightBlueAccent,
|
|
size: 40.0,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
mediaQuery ??= MediaQuery.of(context);
|
|
screenWidth ??= mediaQuery.size.width;
|
|
screenHeight ??= mediaQuery.size.height;
|
|
statusBarHeight ??= mediaQuery.padding.top;
|
|
|
|
_sliverAppBarMaxHeight ??= screenHeight - 150.0;
|
|
|
|
_pageScrollController ??= _shopCoordinator
|
|
.pageScrollController(_sliverAppBarMaxHeight - _sliverAppBarInitHeight);
|
|
|
|
_shopCoordinator.pinnedHeaderSliverHeightBuilder ??= () {
|
|
return statusBarHeight + kToolbarHeight + _tabBarHeight;
|
|
};
|
|
|
|
refresh = false;
|
|
|
|
_slidUpShoppingCart = SlidingUpPanel(
|
|
controller: panelController,
|
|
minHeight: 50.0,
|
|
maxHeight: 250.0,
|
|
isDraggable: true,
|
|
backdropEnabled: true,
|
|
panel: ShoppingCartBar(
|
|
business: _business,
|
|
endKey: endKey,
|
|
onEmptyCartListener: () {
|
|
Future.delayed(Duration(seconds: 1), () {
|
|
panelController.close();
|
|
});
|
|
},
|
|
onPanelOpenCloseRequest: () {
|
|
if (panelController.isPanelOpen) {
|
|
panelController.close();
|
|
} else {
|
|
panelController.open();
|
|
}
|
|
},
|
|
),
|
|
);
|
|
|
|
Row promotRow = Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: <Widget>[],
|
|
);
|
|
for (var i = 0; i < _business.promoProducts.length; i++) {
|
|
promotRow.children.add(Container(
|
|
padding: EdgeInsets.only(
|
|
left: 10.0,
|
|
top: 10.0,
|
|
bottom: 10.0,
|
|
),
|
|
width: 150.0,
|
|
height: 220.0,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: <Widget>[
|
|
GestureDetector(
|
|
child: Container(
|
|
child: Util.showImage(
|
|
_business.promoProducts[i].imagePath,
|
|
),
|
|
),
|
|
onTap: () {
|
|
_showProductDetail(_business.promoProducts[i]);
|
|
},
|
|
),
|
|
Container(
|
|
child: Text(
|
|
_business.promoProducts[i].name,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(fontSize: 14.0),
|
|
),
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: <Widget>[
|
|
ShowPrice(
|
|
_business.promoProducts[i].price,
|
|
currencySign: '\$',
|
|
regularPrice: _business.promoProducts[i].regularPrice,
|
|
),
|
|
Container(
|
|
child: AddRemoveButton(
|
|
product: _business.promoProducts[i],
|
|
business: _business,
|
|
addOnly: true,
|
|
),
|
|
)
|
|
],
|
|
)
|
|
],
|
|
),
|
|
));
|
|
}
|
|
|
|
if (promotRow.children.length > 0) {
|
|
promotHeader = SliverPersistentHeader(
|
|
pinned: false,
|
|
floating: true,
|
|
delegate: _SliverAppBarDelegate2(
|
|
minHeight: 250.0,
|
|
maxHeight: 260.0,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
Container(
|
|
padding: EdgeInsets.only(left: 10.0, top: 5.0),
|
|
child: Text(
|
|
S.of(context).promotions,
|
|
style: TextStyle(fontSize: 15.0),
|
|
),
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.only(right: 10.0),
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: promotRow,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
promotHeader = SliverPersistentHeader(
|
|
pinned: false,
|
|
floating: true,
|
|
delegate: _SliverAppBarDelegate2(
|
|
minHeight: 0.0,
|
|
maxHeight: 0.0,
|
|
child: Center(
|
|
child: Text(''),
|
|
)),
|
|
);
|
|
}
|
|
|
|
Widget listener = Listener(
|
|
onPointerUp: _shopCoordinator.onPointerUp,
|
|
child: CustomScrollView(
|
|
controller: _pageScrollController,
|
|
physics: ClampingScrollPhysics(),
|
|
slivers: <Widget>[
|
|
SliverAppBar(
|
|
pinned: true,
|
|
floating: true,
|
|
snap: true,
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back_ios),
|
|
onPressed: () {
|
|
if (_animationFinish) {
|
|
Routes.router.pop(context);
|
|
}
|
|
}),
|
|
titleSpacing: 0.0,
|
|
actions: <Widget>[
|
|
Padding(
|
|
padding: ButtonTheme.of(context).padding,
|
|
child: GestureDetector(
|
|
child: Icon(Icons.search),
|
|
onTap: () {
|
|
Navigator.push(context,
|
|
MaterialPageRoute(builder: (BuildContext context) {
|
|
return StoreProductSearch(_business);
|
|
}));
|
|
},
|
|
),
|
|
),
|
|
Padding(
|
|
padding: ButtonTheme.of(context).padding,
|
|
child: new GestureDetector(
|
|
child: new Icon(Icons.phone),
|
|
onTap: () {
|
|
Utils.launchURL(
|
|
'tel:${Utils.getFirstNumberFromString(_business.phone)}');
|
|
},
|
|
),
|
|
),
|
|
],
|
|
title: Text(
|
|
_business != null ? _business.name : '',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
backgroundColor: Theme.of(context).primaryColor,
|
|
expandedHeight: _sliverAppBarMaxHeight,
|
|
flexibleSpace: FlexibleSpaceBar(
|
|
background: _buildAppbar(context),
|
|
collapseMode: CollapseMode.none,
|
|
),
|
|
),
|
|
promotHeader,
|
|
SliverPersistentHeader(
|
|
pinned: true,
|
|
floating: false,
|
|
delegate: _SliverAppBarDelegate2(
|
|
minHeight: _tabBarHeight,
|
|
maxHeight: _tabBarHeight,
|
|
child: Container(
|
|
color: Colors.white,
|
|
child: TabBar(
|
|
labelColor: new Color(0xFF3190E8),
|
|
unselectedLabelColor: new Color(0xFF666666),
|
|
indicatorColor: new Color(0xFF3190E8),
|
|
indicatorSize: TabBarIndicatorSize.label,
|
|
labelStyle: new TextStyle(
|
|
fontSize: 16.0,
|
|
),
|
|
controller: _tabController,
|
|
tabs: <Widget>[
|
|
Tab(
|
|
text: S.of(context).products,
|
|
),
|
|
Tab(
|
|
child: Badge(
|
|
badgeContent: Text(
|
|
'${_business.commentsCount}',
|
|
style: TextStyle(color: Colors.white, fontSize: 11.0),
|
|
),
|
|
badgeColor: Colors.lightBlueAccent,
|
|
child: Text(S.of(context).comments),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SliverFillRemaining(
|
|
child: TabBarView(
|
|
controller: _tabController,
|
|
children: <Widget>[
|
|
_buildMainContent(),
|
|
_buildCommentList(),
|
|
],
|
|
),
|
|
),
|
|
// _slidUpShoppingCart,
|
|
],
|
|
),
|
|
);
|
|
|
|
Widget mainBody = listener;
|
|
|
|
Widget widget = Scaffold(
|
|
key: _scaffoldKey,
|
|
body: WillPopScope(
|
|
child: mainBody,
|
|
onWillPop: () async {
|
|
if (_animationFinish) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
),
|
|
);
|
|
|
|
List<Widget> children = [
|
|
Positioned(
|
|
top: 0.0,
|
|
right: 0.0,
|
|
bottom: 50.0,
|
|
left: 0.0,
|
|
child: widget,
|
|
),
|
|
_slidUpShoppingCart,
|
|
];
|
|
|
|
print("pointers: " + _animationPointManager.list.length.toString());
|
|
children += _animationPointManager.list;
|
|
|
|
Stack stack = Stack(
|
|
key: stackKey,
|
|
children: [],
|
|
);
|
|
|
|
stack.children.addAll(children);
|
|
|
|
if (!_business.isPublic) {
|
|
stack.children.add(
|
|
Positioned(
|
|
top: 0,
|
|
right: 0,
|
|
left: screenWidth - 100,
|
|
bottom: screenHeight - 100,
|
|
child: Image.asset(
|
|
'assets/images/under_renovation.png',
|
|
width: 200.0,
|
|
height: 200.0,
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return stack;
|
|
}
|
|
|
|
SnackBar commentImageSnackBar(String url) {
|
|
return SnackBar(
|
|
elevation: 0.0,
|
|
action: SnackBarAction(
|
|
label: S.of(context).close,
|
|
onPressed: () {
|
|
_scaffoldKey.currentState.hideCurrentSnackBar();
|
|
},
|
|
),
|
|
content: Container(
|
|
height: 260.0,
|
|
child: Util.showImage(
|
|
'$url',
|
|
width: 180.0,
|
|
height: 180.0,
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
CupertinoActionSheet commentImageActionSheet(String url) {
|
|
return CupertinoActionSheet(
|
|
message: Container(
|
|
height: 280.0,
|
|
child: Util.showImage(
|
|
'$url',
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
cancelButton: CupertinoActionSheetAction(
|
|
child: Text(S.of(context).close),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCommentList() {
|
|
Widget commentWidget = Center(
|
|
child: Text(S.of(context).no_comments_yet),
|
|
);
|
|
if (comments != null && comments.length > 0) {
|
|
commentWidget = ListView.builder(
|
|
controller: _listScrollController3,
|
|
itemCount: comments.length,
|
|
itemBuilder: (BuildContext context, int position) {
|
|
Comment comment = comments[position];
|
|
Row imageRow = Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: <Widget>[],
|
|
);
|
|
if (comment.images.length > 0) {
|
|
for (ProductImage image in comment.images) {
|
|
imageRow.children.add(
|
|
GestureDetector(
|
|
child: Container(
|
|
padding: EdgeInsets.all(5.0),
|
|
child: Util.showImage(
|
|
'https:${image.image}',
|
|
width: 40.0,
|
|
height: 40.0,
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
onTap: () {
|
|
// _scaffoldKey.currentState.showSnackBar(commentImageSnackBar('https:${image.image}'));
|
|
showCupertinoModalPopup(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return commentImageActionSheet(
|
|
'https:${image.image}');
|
|
});
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
Widget replyWidget = SizedBox.shrink();
|
|
if (comment.replyFromStore != null &&
|
|
comment.replyFromStore.isNotEmpty) {
|
|
replyWidget = Container(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.only(top: 10.0, bottom: 5.0),
|
|
child: Text(
|
|
S.of(context).response_from_store,
|
|
style: TextStyle(
|
|
fontSize: 12.0,
|
|
color: Colors.black38,
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.only(bottom: 16.0),
|
|
child: ReadMoreText(
|
|
comment.replyFromStore,
|
|
style: TextStyle(
|
|
fontSize: 13.0,
|
|
color: Colors.black54,
|
|
),
|
|
trimLines: 2,
|
|
trimLength: 40,
|
|
colorClickableText: Colors.black,
|
|
trimMode: TrimMode.Line,
|
|
trimCollapsedText: S.of(context).show_more,
|
|
trimExpandedText: S.of(context).show_less,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
return Container(
|
|
padding: EdgeInsets.only(
|
|
left: 16.0, right: 16.0, top: 16.0, bottom: 16.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
Container(
|
|
padding: EdgeInsets.only(bottom: 10.0),
|
|
margin: EdgeInsets.only(bottom: 5.0),
|
|
width: double.infinity,
|
|
child: ReadMoreText(
|
|
comment.content,
|
|
trimLines: 3,
|
|
trimLength: 66,
|
|
colorClickableText: Colors.blue,
|
|
trimMode: TrimMode.Length,
|
|
trimCollapsedText: S.of(context).show_more,
|
|
trimExpandedText: S.of(context).show_less,
|
|
),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
width: 0.5,
|
|
color: Colors.black12,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: <Widget>[
|
|
Container(
|
|
child: SmoothStarRating(
|
|
starCount: 5,
|
|
rating: comment.rating.toDouble(),
|
|
size: 12.0,
|
|
filledIconData: Icons.star,
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
Container(
|
|
child: Text(
|
|
Utils.safeString(comment.contact) +
|
|
' ' +
|
|
Utils.utcDatetimeStringToLocalDatetimeString(
|
|
context, comment.createdAt),
|
|
style: TextStyle(
|
|
fontSize: 12.0,
|
|
color: Colors.black26,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
child: imageRow,
|
|
),
|
|
replyWidget,
|
|
],
|
|
),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
width: 10.0,
|
|
color: Colors.black12,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
return SmartRefresher(
|
|
enablePullUp: true,
|
|
enablePullDown: 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: _commentRefreshController,
|
|
onRefresh: _onCommentRefresh,
|
|
onLoading: _onCommentLoadMore,
|
|
child: commentWidget,
|
|
);
|
|
}
|
|
|
|
Widget _buildAppbar(BuildContext context) {
|
|
var addressRow = new Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[],
|
|
);
|
|
addressRow.children.add(new Icon(
|
|
Icons.location_on,
|
|
size: 14.0,
|
|
color: const Color(0xFFEEEEEE),
|
|
));
|
|
var addressColumn = new Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[],
|
|
);
|
|
addressColumn.children.add(new Text(
|
|
_business.address.addressLine1 +
|
|
(_business.address.addressLine2.isNotEmpty
|
|
? ' ' + _business.address.addressLine2
|
|
: ''),
|
|
style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)),
|
|
));
|
|
addressColumn.children.add(new Text(
|
|
_business.address.city +
|
|
', ' +
|
|
_business.address.state +
|
|
', ' +
|
|
_business.address.zip,
|
|
style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)),
|
|
));
|
|
|
|
addressRow.children.add(addressColumn);
|
|
|
|
var distanceRow = new Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[],
|
|
);
|
|
if (_business.distanceInfo != null) {
|
|
distanceRow.children.add(new Icon(
|
|
Icons.directions_car,
|
|
size: 14.0,
|
|
color: Colors.white,
|
|
));
|
|
distanceRow.children.add(new Text(
|
|
_business.distanceInfo.distance != null
|
|
? _business.distanceInfo.distance.text
|
|
: '***' + ' / ',
|
|
style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0),
|
|
));
|
|
var duration = Duration(
|
|
seconds: (_business.distanceInfo.duration != null
|
|
? _business.distanceInfo.duration.value
|
|
: 30) +
|
|
_business.shippingTime * 60);
|
|
var hours = duration.inHours.remainder(60);
|
|
var minutes = duration.inMinutes.remainder(60);
|
|
distanceRow.children.add(new Text(
|
|
hours > 0 ? S.of(context).hour_token(hours) : '',
|
|
style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0),
|
|
));
|
|
distanceRow.children.add(new Text(
|
|
minutes > 0
|
|
? (hours > 0 ? ' ' : '') + S.of(context).minute_token(minutes)
|
|
: '',
|
|
style: new TextStyle(color: const Color(0xFFEEEEEE), fontSize: 13.0),
|
|
));
|
|
distanceRow.children.add(
|
|
new Text(
|
|
' / ' +
|
|
S.of(context).min_order_amount_token(_business.minPrice) +
|
|
' ',
|
|
style: new TextStyle(fontSize: 13.0, color: const Color(0xFFEEEEEE)),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget widget = new PreferredSize(
|
|
preferredSize: new Size.fromHeight(90.0),
|
|
child: new Container(
|
|
height: 90.0,
|
|
padding: new EdgeInsets.all(10.0),
|
|
child: new Row(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
new Container(
|
|
width: 72.0,
|
|
height: 72.0,
|
|
margin: new EdgeInsets.only(right: 10.0),
|
|
child: Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 0.0,
|
|
left: 0.0,
|
|
right: 0.0,
|
|
bottom: 0.0,
|
|
child: GestureDetector(
|
|
child: Util.showImage(
|
|
'${_business.picUrl}',
|
|
width: 72.0,
|
|
height: 72.0,
|
|
fit: BoxFit.fill,
|
|
),
|
|
onTap: () {
|
|
// Utils.addRemoveFavorite(_business.id);
|
|
},
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 0.0,
|
|
left: 0.0,
|
|
right: -50.0,
|
|
bottom: -50.0,
|
|
child: Icon(
|
|
Icons.favorite,
|
|
color: Colors.transparent,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
new Expanded(
|
|
child: new Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
addressRow,
|
|
new Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
new Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
new Icon(
|
|
Icons.phone,
|
|
size: 14.0,
|
|
color: Colors.white,
|
|
),
|
|
new Text(
|
|
_business.phone,
|
|
style: new TextStyle(
|
|
fontSize: 13.0, color: const Color(0xFFEEEEEE)),
|
|
)
|
|
],
|
|
),
|
|
new Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
new Icon(
|
|
Icons.av_timer,
|
|
size: 14.0,
|
|
color: Colors.white,
|
|
),
|
|
new Text(
|
|
_business.openingTime[0].openTime +
|
|
':00 - ' +
|
|
_business.openingTime[0].closeTime +
|
|
':00',
|
|
style: new TextStyle(
|
|
fontSize: 13.0, color: const Color(0xFFEEEEEE)),
|
|
)
|
|
],
|
|
)
|
|
],
|
|
),
|
|
distanceRow,
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
List<Widget> galleryPages = _buildBanners(context);
|
|
slidingGellery = Carousel(
|
|
height: 168.0,
|
|
pages: galleryPages,
|
|
autoPlay: true,
|
|
);
|
|
|
|
Widget column = new Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
_business.bulletin.isNotEmpty ? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
Container(
|
|
padding:
|
|
EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0),
|
|
child: Icon(
|
|
Icons.announcement,
|
|
size: 14.0,
|
|
color: Colors.white,
|
|
)),
|
|
Container(
|
|
width: mediaQuery.size.width - 30.0,
|
|
padding: EdgeInsets.only(right: 10.0, bottom: 10.0),
|
|
child: new Text(
|
|
_business.bulletin.isEmpty ? '' : _business.bulletin,
|
|
softWrap: true,
|
|
style: new TextStyle(
|
|
fontSize: 12.0, color: const Color(0xFFDDDDDD)),
|
|
// overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
) : SizedBox.shrink(),
|
|
_business.description.isNotEmpty ? Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
Container(
|
|
padding: EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0),
|
|
child: Icon(
|
|
Icons.store,
|
|
size: 14.0,
|
|
color: Colors.white,
|
|
)),
|
|
Container(
|
|
padding: EdgeInsets.only(right: 10.0, bottom: 10.0),
|
|
child: new Text(
|
|
S.of(context).store_introduction,
|
|
style: new TextStyle(
|
|
fontSize: 13.0,
|
|
color: const Color(0xFFDDDDDD),
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Container(
|
|
padding:
|
|
EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
|
|
child: Text(
|
|
_business.description,
|
|
style: TextStyle(
|
|
color: Colors.lightGreen,
|
|
fontSize: 12.0,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
) : SizedBox.shrink(),
|
|
galleryPages.isNotEmpty ? Container(
|
|
padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
|
|
child: slidingGellery,
|
|
) : SizedBox.shrink(),
|
|
_business.policy.isNotEmpty ? Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
Container(
|
|
padding: EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0),
|
|
child: Icon(
|
|
Icons.store,
|
|
size: 14.0,
|
|
color: Colors.white,
|
|
)),
|
|
Container(
|
|
padding: EdgeInsets.only(right: 10.0, bottom: 10.0),
|
|
child: new Text(
|
|
S.of(context).store_policy,
|
|
style: new TextStyle(
|
|
fontSize: 13.0,
|
|
color: const Color(0xFFDDDDDD),
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
|
|
child: SingleChildScrollView(
|
|
child: Text(
|
|
_business.policy,
|
|
softWrap: true,
|
|
style: TextStyle(
|
|
fontSize: 10.0,
|
|
color: const Color(0xFFEEEEEE),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
) : SizedBox.shrink(),
|
|
],
|
|
);
|
|
|
|
return Stack(
|
|
children: <Widget>[
|
|
Positioned(
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
child: Util.showImage(
|
|
_business.bannerImageUrl,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
child: Container(
|
|
color: Color(0x88000000),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: kToolbarHeight + 20.0,
|
|
left: 0,
|
|
right: 0,
|
|
child: widget,
|
|
),
|
|
Positioned(
|
|
top: kToolbarHeight + 20.0 + 90.0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
child: Container(
|
|
child: SingleChildScrollView(
|
|
child: column,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildBanners(BuildContext context) {
|
|
var pages = <Widget>[];
|
|
for (var i = 0; i < _business.slideImages.length; i++) {
|
|
pages.add(new GestureDetector(
|
|
child: new Container(
|
|
child: Util.showImage(
|
|
_business.slideImages[i].imageUrl,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
onTap: () {},
|
|
));
|
|
}
|
|
return pages;
|
|
}
|
|
|
|
Widget _buildMainContent() {
|
|
return new Row(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
_buildCategories(),
|
|
Expanded(
|
|
child: _buildProducts(),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildCategories() {
|
|
return new Container(
|
|
width: 100.0,
|
|
color: new Color(0xFFF5F5F5),
|
|
child: new SizedBox.expand(
|
|
child: new ListView.builder(
|
|
physics: ClampingScrollPhysics(),
|
|
controller: _listScrollController2,
|
|
itemCount: _categoryProducts == null ? 0 : _categoryProducts.length,
|
|
itemBuilder: (BuildContext context, int i) {
|
|
CategoryProducts cp = _categoryProducts[i];
|
|
int qtyInCategory = 0;
|
|
CartInfo cartInfo =
|
|
Utils.getCartInfoByBusiness(store.state.cartInfos, _business);
|
|
if (cartInfo != null &&
|
|
cartInfo.businessInfo.id == _business.id &&
|
|
cartInfo.productList != null) {
|
|
for (var i = 0; i < cartInfo.productList.length; i++) {
|
|
if (cartInfo.productList[i].product.categoryId == cp.id) {
|
|
qtyInCategory += cartInfo.productList[i].quantity.round();
|
|
}
|
|
}
|
|
}
|
|
Row categoryRow = Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[],
|
|
);
|
|
categoryRow.children.add(
|
|
Expanded(
|
|
flex: 1,
|
|
child: Container(
|
|
margin: EdgeInsets.only(top: 5.0, bottom: 5.0),
|
|
child: Text(
|
|
cp.name,
|
|
style: TextStyle(
|
|
fontSize: 13.0,
|
|
fontWeight: (cp.id == Constants.FEATURED_PRODUCT_ID ||
|
|
cp.id == Constants.HOT_SALE_ID)
|
|
? FontWeight.bold
|
|
: FontWeight.normal,
|
|
color: (cp.id == Constants.HOT_SALE_ID)
|
|
? Colors.redAccent
|
|
: ((cp.id == Constants.FEATURED_PRODUCT_ID)
|
|
? Colors.lightGreen
|
|
: Colors.black87)),
|
|
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
|
|
overflow: TextOverflow.ellipsis,
|
|
softWrap: true,
|
|
maxLines: 2,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
if (qtyInCategory > 0) {
|
|
categoryRow.children.add(
|
|
new Badge(
|
|
badgeContent: Text(
|
|
'$qtyInCategory',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11.0,
|
|
),
|
|
),
|
|
animationType: BadgeAnimationType.scale,
|
|
),
|
|
);
|
|
} else {
|
|
categoryRow.children.add(SizedBox.shrink());
|
|
}
|
|
return new GestureDetector(
|
|
child: new Container(
|
|
height: 70.0,
|
|
padding: new EdgeInsets.symmetric(horizontal: 10.0),
|
|
decoration: new BoxDecoration(
|
|
color: _categoryIndex == i ? Style.backgroundColor : null,
|
|
border: new Border(
|
|
bottom:
|
|
new BorderSide(color: new Color(0xFFEBEBEB)))),
|
|
child: categoryRow,
|
|
),
|
|
onTap: () => _selectCategory(i),
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _selectCategory(int index) {
|
|
if (displayProductByCategoryClick && _categoryProducts[index].id > 0) {
|
|
categoryId = _categoryProducts[index].id;
|
|
loadProducts();
|
|
return;
|
|
}
|
|
double height = 0.0;
|
|
for (int i = 0; i < index; ++i) {
|
|
height += _categoryDescHeight +
|
|
_categoryProducts[i].products.length * _productHeight;
|
|
}
|
|
if (height > _listScrollController1.position.maxScrollExtent) {
|
|
height = _listScrollController1.position.maxScrollExtent;
|
|
}
|
|
_categoryIndexChange = true;
|
|
_listScrollController1
|
|
.animateTo(height,
|
|
duration: new Duration(
|
|
microseconds: 200,
|
|
),
|
|
curve: Curves.linear)
|
|
.then((value) {
|
|
_categoryIndexChange = false;
|
|
});
|
|
print(
|
|
'height: $height, index: $index, ${_categoryProducts[0].products.length}');
|
|
if (mounted) {
|
|
setState(() {
|
|
_categoryIndex = index;
|
|
});
|
|
}
|
|
}
|
|
|
|
CategoryProducts getCategoryProductByCategoryId(int cid) {
|
|
for (CategoryProducts cp in _categoryProducts) {
|
|
if (cp.id == cid) {
|
|
return cp;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _resetProductListScroll() {
|
|
if (_listScrollController1 != null &&
|
|
_listScrollController1.positions.isNotEmpty) {
|
|
_listScrollController1.animateTo(
|
|
0,
|
|
duration: new Duration(
|
|
microseconds: 200,
|
|
),
|
|
curve: Curves.linear,
|
|
);
|
|
}
|
|
}
|
|
|
|
int _getCategoryIndexByRightScrollHeight(double height) {
|
|
double cHeight = 0.0;
|
|
if (height > 0) {
|
|
for (int i = 0; i < _categoryProducts.length; ++i) {
|
|
double categoryHeight = _categoryDescHeight +
|
|
_categoryProducts[i].products.length * _productHeight;
|
|
if (height >= cHeight && height < cHeight + categoryHeight) {
|
|
return i;
|
|
}
|
|
cHeight += categoryHeight;
|
|
}
|
|
}
|
|
if (height > cHeight) {
|
|
return _categoryProducts.length - 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void _setCategoryByProductScrolledIndex(int index) {
|
|
if (index == _categoryIndex) {
|
|
return;
|
|
}
|
|
double height = 0.0;
|
|
for (int i = 0; i < index; ++i) {
|
|
height += _categoryHeight;
|
|
}
|
|
if (height > _listScrollController2.position.maxScrollExtent) {
|
|
height = _listScrollController2.position.maxScrollExtent;
|
|
}
|
|
_listScrollController2.animateTo(height,
|
|
duration: new Duration(
|
|
milliseconds: 200,
|
|
),
|
|
curve: Curves.linear);
|
|
if (mounted) {
|
|
setState(() {
|
|
_categoryIndex = index;
|
|
});
|
|
}
|
|
}
|
|
|
|
int numCategoriesHasProducts() {
|
|
int num = 0;
|
|
for (CategoryProducts cp in _categoryProducts) {
|
|
if (cp.products.length > 0) {
|
|
num += 1;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
Widget _buildProducts() {
|
|
return new SizedBox.expand(
|
|
child: new NotificationListener<ScrollUpdateNotification>(
|
|
onNotification: (ScrollUpdateNotification notification) {
|
|
if (!displayProductByCategoryClick) {
|
|
if (_categoryIndexChange) {
|
|
return;
|
|
}
|
|
int index = _getCategoryIndexByRightScrollHeight(
|
|
notification.metrics.pixels);
|
|
_setCategoryByProductScrolledIndex(index);
|
|
}
|
|
return;
|
|
},
|
|
child: new ListView.builder(
|
|
physics: ClampingScrollPhysics(),
|
|
controller: _listScrollController1,
|
|
// itemCount: displayProductByCategoryClick ? 1 : (_categoryProducts == null ? 0 : _categoryProducts.length),
|
|
itemCount:
|
|
_categoryProducts == null ? 0 : numCategoriesHasProducts(),
|
|
itemBuilder: (BuildContext context, int i) {
|
|
CategoryProducts cp;
|
|
cp = _categoryProducts[i];
|
|
|
|
int index = -1;
|
|
if (displayProductByCategoryClick) {
|
|
if (categoryId > 0) {
|
|
for (var j = 0; j < _categoryProducts.length; j++) {
|
|
if (_categoryProducts[j].id == categoryId) {
|
|
cp = _categoryProducts[j];
|
|
index = j;
|
|
break;
|
|
}
|
|
}
|
|
if (cp == null) {
|
|
index = 0;
|
|
cp = _categoryProducts[0];
|
|
}
|
|
}
|
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
_setCategoryByProductScrolledIndex(index);
|
|
});
|
|
}
|
|
|
|
if (cp.products.length == 0) {
|
|
return SizedBox.shrink();
|
|
}
|
|
|
|
Column col = new Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[],
|
|
);
|
|
col.children.add(new Container(
|
|
height: _categoryDescHeight,
|
|
padding: new EdgeInsets.symmetric(horizontal: 10.0),
|
|
color: new Color(0xFFF5F5F5),
|
|
child: new Row(
|
|
children: <Widget>[
|
|
new Expanded(
|
|
child: new Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
textBaseline: TextBaseline.alphabetic,
|
|
children: <Widget>[
|
|
new Padding(
|
|
padding: new EdgeInsets.only(right: 5.0),
|
|
child: new Text(
|
|
cp.name,
|
|
style: new TextStyle(
|
|
fontSize: 16.0,
|
|
fontWeight:
|
|
(cp.id == Constants.FEATURED_PRODUCT_ID ||
|
|
cp.id == Constants.HOT_SALE_ID)
|
|
? FontWeight.bold
|
|
: FontWeight.normal,
|
|
color: (cp.id == Constants.HOT_SALE_ID)
|
|
? Colors.redAccent
|
|
: ((cp.id ==
|
|
Constants.FEATURED_PRODUCT_ID)
|
|
? Colors.lightGreen
|
|
: Colors.black87)),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
new Visibility(
|
|
visible: cp.description.isNotEmpty,
|
|
child: new Text(
|
|
cp.description,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: new TextStyle(
|
|
fontSize: 10.0,
|
|
color: new Color(0xFF999999)),
|
|
),
|
|
replacement: const SizedBox.shrink()),
|
|
],
|
|
))
|
|
],
|
|
)));
|
|
for (var p in cp.products) {
|
|
var pStack = new Stack(
|
|
children: <Widget>[],
|
|
);
|
|
|
|
pStack.children.add(new ProductItem(
|
|
product: p,
|
|
business: _business,
|
|
));
|
|
|
|
col.children.add(pStack);
|
|
}
|
|
if (displayProductByCategoryClick) {
|
|
if (displayProductByCategoryClickIndicator.isNotEmpty) {
|
|
col.children.add(
|
|
Container(
|
|
padding: EdgeInsets.all(12.0),
|
|
child: Center(
|
|
child: Text(
|
|
displayProductByCategoryClickIndicator,
|
|
style: TextStyle(
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
if (cp.products.length < Constants.ORDERS_PER_PAGE) {
|
|
col.children.add(
|
|
Container(
|
|
padding: EdgeInsets.all(12.0),
|
|
child: Center(
|
|
child: Text(
|
|
S.of(context).end_of_the_list,
|
|
style: TextStyle(
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
col.children.add(
|
|
Container(
|
|
padding: EdgeInsets.all(12.0),
|
|
child: Center(
|
|
child: Text(
|
|
S.of(context).pull_up_to_load_more,
|
|
style: TextStyle(
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return col;
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_shopCoordinator = ShopScrollCoordinator();
|
|
_tabController = TabController(vsync: this, length: 2);
|
|
|
|
refresh = false;
|
|
|
|
_listScrollController1 = _shopCoordinator.newChildScrollController();
|
|
_listScrollController2 = _shopCoordinator.newChildScrollController();
|
|
_listScrollController3 = _shopCoordinator.newChildScrollController();
|
|
|
|
_listScrollController1.addListener(() {
|
|
if (_listScrollController1.position.atEdge) {
|
|
if (_listScrollController1.position.pixels == 0) {
|
|
print('product list at top');
|
|
} else {
|
|
print('product list at bottom');
|
|
if (displayProductByCategoryClick && _productCurrentPage != 0) {
|
|
loadMoreProducts();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
eventBus.on<OnCartInfoUpdated>().listen((event) {
|
|
if (mounted) {
|
|
setState(() {
|
|
refresh = true;
|
|
});
|
|
}
|
|
});
|
|
|
|
// onProductWillAddToCartSubscription =
|
|
// eventBus.on<OnProductWillAddToCart>().listen((event) {
|
|
// if (mounted) {
|
|
// itemAddToCart(event.buttonKey);
|
|
//
|
|
// CartInfo cartInfo =
|
|
// Utils.getCartInfoByBusiness(store.state.cartInfos, event.business);
|
|
//
|
|
// if (cartInfo == null || cartInfo.productList.length == 0) {
|
|
// cartInfo = CartInfo();
|
|
// cartInfo.id = 0;
|
|
// cartInfo.amountPaid = 0.0;
|
|
// cartInfo.businessInfo = event.business;
|
|
// cartInfo.extraFeeList = [];
|
|
// cartInfo.discountList = [];
|
|
// CartLineItem lineItem = _newCartLineItem(
|
|
// id: 0,
|
|
// price: event.price,
|
|
// product: event.product,
|
|
// name: event.product.name,
|
|
// description: event.description,
|
|
// quantity: 1.0);
|
|
// cartInfo.productList = [lineItem];
|
|
// store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList(
|
|
// store.state.cartInfos, cartInfo)));
|
|
// eventBus.fire(new OnCartInfoUpdated());
|
|
// } else {
|
|
// if (event.product.productAttributes.length > 0) {
|
|
// CartLineItem lineItem = _newCartLineItem(
|
|
// id: 0,
|
|
// price: event.price,
|
|
// product: event.product,
|
|
// name: event.product.name,
|
|
// description: event.description,
|
|
// quantity: 1.0);
|
|
// cartInfo.productList.add(lineItem);
|
|
// store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList(
|
|
// store.state.cartInfos, cartInfo)));
|
|
// eventBus.fire(new OnCartInfoUpdated());
|
|
// } else {
|
|
// int found = -1;
|
|
// for (var i = 0; i < cartInfo.productList.length; i++) {
|
|
// if (event.product.id == cartInfo.productList[i].product.id) {
|
|
// found = i;
|
|
// break;
|
|
// }
|
|
// }
|
|
// if (found == -1) {
|
|
// CartLineItem lineItem = _newCartLineItem(
|
|
// id: 0,
|
|
// price: event.price,
|
|
// product: event.product,
|
|
// name: event.product.name,
|
|
// description: event.description,
|
|
// quantity: 1.0);
|
|
// cartInfo.productList.add(lineItem);
|
|
// store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList(
|
|
// store.state.cartInfos, cartInfo)));
|
|
// eventBus.fire(new OnCartInfoUpdated());
|
|
// } else {
|
|
// if (cartInfo.productList[found].quantity + 1.0 >
|
|
// event.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[found].quantity += 1;
|
|
// store.dispatch(new UpdateCartInfo(
|
|
// Utils.addCartInfoToCartInfoList(
|
|
// store.state.cartInfos, cartInfo)));
|
|
// eventBus.fire(new OnCartInfoUpdated());
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// });
|
|
// onProductWillRemoveFromCartSubscription =
|
|
// eventBus.on<OnProductWillRemoveFromCart>().listen((event) {
|
|
// if (mounted) {
|
|
// CartInfo cartInfo =
|
|
// Utils.getCartInfoByBusiness(store.state.cartInfos, event.business);
|
|
// if (cartInfo != null) {
|
|
// if (cartInfo.productList.length > 0) {
|
|
// if (event.productListIndex != -1) {
|
|
// if (cartInfo.productList[event.productListIndex].quantity <= 1) {
|
|
// cartInfo.productList.removeAt(event.productListIndex);
|
|
// } else {
|
|
// cartInfo.productList[event.productListIndex].quantity -= 1;
|
|
// }
|
|
// } else {
|
|
// int productListIndex = -1;
|
|
// for (var i = 0; i < cartInfo.productList.length; i++) {
|
|
// if (cartInfo.productList[i].product.id == event.product.id) {
|
|
// productListIndex = i;
|
|
// break;
|
|
// }
|
|
// }
|
|
// if (productListIndex != -1) {
|
|
// if (cartInfo.productList[productListIndex].quantity <= 1) {
|
|
// cartInfo.productList.removeAt(productListIndex);
|
|
// } else {
|
|
// cartInfo.productList[productListIndex].quantity -= 1;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if (cartInfo.productList.length <= 0) {
|
|
// store.dispatch(UpdateCartInfo(Utils.removeCartInfoFromCartInfoList(
|
|
// store.state.cartInfos, cartInfo)));
|
|
// } else {
|
|
// store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList(
|
|
// store.state.cartInfos, cartInfo)));
|
|
// }
|
|
// eventBus.fire(new OnCartInfoUpdated());
|
|
// }
|
|
// }
|
|
// });
|
|
|
|
loadProducts();
|
|
}
|
|
|
|
void loadProducts() {
|
|
if (categoryId < 0) {
|
|
return;
|
|
}
|
|
if (_categoryProducts != null) {
|
|
_isLoading = true;
|
|
Utils.showLoadingDialog(context, message: S.of(context).loading);
|
|
}
|
|
_productCurrentPage = 1;
|
|
Utils.loadProducts(
|
|
widget.businessId, categoryId, _productCurrentPage, false,
|
|
(value) {
|
|
if (_isLoading) {
|
|
_isLoading = false;
|
|
Navigator.of(context).pop();
|
|
}
|
|
if (mounted) {
|
|
setState(() {
|
|
displayProductByCategoryClick =
|
|
value['display_product_by_category_click'];
|
|
displayProductByCategoryClickIndicator = '';
|
|
_business = Business.fromJson(value['business']);
|
|
_categoryProducts = (value['category_products'] as List)
|
|
.map((i) => CategoryProducts.fromJson(i))
|
|
.toList();
|
|
_featuredProducts = (value['featured_products'] as List)
|
|
.map((i) => Product.fromJson(i))
|
|
.toList();
|
|
_hotSaleProducts = (value['hot_sale_products'] as List)
|
|
.map((i) => Product.fromJson(i))
|
|
.toList();
|
|
_prompts = value['prompt'] as List;
|
|
|
|
if (_hotSaleProducts.length > 0) {
|
|
CategoryProducts hs = CategoryProducts(
|
|
Constants.HOT_SALE_ID,
|
|
widget.businessId,
|
|
S.of(context).hot_sale,
|
|
'',
|
|
'',
|
|
_hotSaleProducts);
|
|
_categoryProducts.insert(0, hs);
|
|
}
|
|
|
|
if (_featuredProducts.length > 0) {
|
|
CategoryProducts fe = CategoryProducts(
|
|
Constants.FEATURED_PRODUCT_ID,
|
|
widget.businessId,
|
|
S.of(context).featured_product,
|
|
'',
|
|
'',
|
|
_featuredProducts);
|
|
_categoryProducts.insert(0, fe);
|
|
}
|
|
|
|
checkActionAndClose(context);
|
|
|
|
if (displayProductByCategoryClick) {
|
|
_resetProductListScroll();
|
|
}
|
|
});
|
|
}
|
|
},
|
|
(error) {
|
|
print('error: $error');
|
|
if (_isLoading) {
|
|
_isLoading = false;
|
|
Navigator.of(context).pop();
|
|
}
|
|
Utils.showMessageDialog(context, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
void loadMoreProducts() {
|
|
if (_productCurrentPage == 0 || categoryId <= 0) {
|
|
return;
|
|
}
|
|
_productCurrentPage += 1;
|
|
Utils.loadProducts(widget.businessId, categoryId, _productCurrentPage, true,
|
|
(value) {
|
|
if (_isLoading) {
|
|
_isLoading = false;
|
|
Navigator.of(context).pop();
|
|
}
|
|
if (mounted) {
|
|
setState(() {
|
|
List<CategoryProducts> moreCategoryProducts =
|
|
(value as List).map((i) => CategoryProducts.fromJson(i)).toList();
|
|
if (moreCategoryProducts.isEmpty) {
|
|
_productCurrentPage = 0;
|
|
displayProductByCategoryClickIndicator =
|
|
S.of(context).end_of_the_list;
|
|
} else {
|
|
if (moreCategoryProducts[0].products.length < Constants.ORDERS_PER_PAGE) {
|
|
_productCurrentPage = 0;
|
|
displayProductByCategoryClickIndicator =
|
|
S.of(context).end_of_the_list;
|
|
} else {
|
|
displayProductByCategoryClickIndicator =
|
|
S.of(context).pull_up_to_load_more;
|
|
}
|
|
CategoryProducts currentCp =
|
|
getCategoryProductByCategoryId(categoryId);
|
|
if (currentCp != null) {
|
|
currentCp.products.addAll(moreCategoryProducts[0].products);
|
|
} else {
|
|
_productCurrentPage = 0;
|
|
displayProductByCategoryClickIndicator =
|
|
S.of(context).end_of_the_list;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
(error) {
|
|
print('error: $error');
|
|
if (_isLoading) {
|
|
_isLoading = false;
|
|
Navigator.of(context).pop();
|
|
}
|
|
Utils.showMessageDialog(context, error);
|
|
}
|
|
);
|
|
}
|
|
|
|
CartLineItem _newCartLineItem(
|
|
{int id,
|
|
double price,
|
|
Product product,
|
|
String name,
|
|
String description,
|
|
double quantity}) {
|
|
CartLineItem lineItem = CartLineItem();
|
|
lineItem.unitPrice = price;
|
|
lineItem.product = product;
|
|
lineItem.name = product.name;
|
|
lineItem.description = description;
|
|
lineItem.quantity = quantity;
|
|
return lineItem;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
print('Shop disposed!!!');
|
|
// onProductWillAddToCartSubscription?.cancel();
|
|
// onProductWillRemoveFromCartSubscription?.cancel();
|
|
_tabController?.dispose();
|
|
_pageScrollController?.dispose();
|
|
_commentRefreshController?.dispose();
|
|
_listScrollController1?.dispose();
|
|
_listScrollController2?.dispose();
|
|
_listScrollController3?.dispose();
|
|
_animationPointManager?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
}
|
|
|
|
void _showProductDetail(Product p) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => ProductDetailPage(
|
|
product: p,
|
|
business: _business,
|
|
)),
|
|
);
|
|
}
|
|
|
|
void checkActionAndClose(BuildContext mainContext) {
|
|
// if (widget.action == Constants.ACTION_COUPON && store.state.referrerContactId != null && store.state.referrerContactId > 0) {
|
|
// showDialog(
|
|
// context: context,
|
|
// barrierDismissible: false,
|
|
// builder: (BuildContext context) {
|
|
// return AlertDialog(
|
|
// title: Text(S.of(context).coupon),
|
|
// content: Text(
|
|
// S.of(context).a_friend_send_you_a_coupon
|
|
// ),
|
|
// actions: [
|
|
// FlatButton(
|
|
// child: Text(S.of(context).no_thanks),
|
|
// onPressed: () {
|
|
// Navigator.of(context).pop();
|
|
// checkClose(mainContext, _business);
|
|
// },
|
|
// ),
|
|
// RaisedButton(
|
|
// child: Text(
|
|
// S.of(context).yes,
|
|
// style: TextStyle(
|
|
// color: Colors.white,
|
|
// ),
|
|
// ),
|
|
// color: Theme.of(context).primaryColor,
|
|
// onPressed: () {
|
|
// Utils.showLoadingDialog(mainContext, message: S.of(context).processing);
|
|
// HttpUtil.httpGet('v1/get-referrer-coupon',
|
|
// businessId: _business.id,
|
|
// queryParameters: {
|
|
// 'referrer_contact_id': store.state.referrerContactId,
|
|
// }
|
|
// ).then((data) {
|
|
// Navigator.of(context).pop();
|
|
// Coupon coupon = Coupon.fromJson(data);
|
|
// showDialog(
|
|
// context: mainContext,
|
|
// barrierDismissible: false,
|
|
// builder: (BuildContext context) {
|
|
// return AlertDialog(
|
|
// title: Text(
|
|
// S.of(context).congratulation,
|
|
// ),
|
|
// content: Text(
|
|
// S.of(context).got_a_coupon_token(coupon.valueAmount)
|
|
// ),
|
|
// actions: [
|
|
// FlatButton(
|
|
// child: Text(
|
|
// S.of(context).close,
|
|
// ),
|
|
// onPressed: () {
|
|
// Navigator.of(context).pop();
|
|
// Navigator.of(context).pop();
|
|
// },
|
|
// ),
|
|
// ],
|
|
// );
|
|
// }
|
|
// );
|
|
// }).catchError((error) {
|
|
// Navigator.of(context).pop();
|
|
// Navigator.of(context).pop();
|
|
// if (error is DioError) {
|
|
// if (error.response.data['code'] == 1013) {
|
|
// showDialog(
|
|
// context: mainContext,
|
|
// barrierDismissible: false,
|
|
// builder: (BuildContext context) {
|
|
// return AlertDialog(
|
|
// title: Text(S.of(context).error),
|
|
// content: Text(error.response.data['message']),
|
|
// actions: [
|
|
// FlatButton(
|
|
// child: Text(S.of(context).cancel),
|
|
// onPressed: () {
|
|
// Navigator.of(context).pop();
|
|
// },
|
|
// ),
|
|
// RaisedButton(
|
|
// color: Theme.of(context).primaryColor,
|
|
// child: Text(
|
|
// S.of(context).login,
|
|
// style: TextStyle(
|
|
// color: Colors.white,
|
|
// ),
|
|
// ),
|
|
// onPressed: () {
|
|
// Navigator.of(context).pop();
|
|
// store.dispatch(UpdateRedirectRoute('/shop/${widget.businessId}/${widget.deviceId}/${widget.tableNo}/${widget.action}'));
|
|
// Routes.router.navigateTo(context, '/login');
|
|
// },
|
|
// ),
|
|
// ],
|
|
// );
|
|
// }
|
|
// );
|
|
// } else {
|
|
// Utils.showMessageDialog(mainContext, error);
|
|
// }
|
|
// } else {
|
|
// Utils.showMessageDialog(mainContext, error);
|
|
// }
|
|
// });
|
|
// },
|
|
// )
|
|
// ],
|
|
// );
|
|
// }
|
|
// );
|
|
// } else {
|
|
// checkClose(context, _business);
|
|
// }
|
|
}
|
|
|
|
void checkClose(BuildContext context, Business business) {
|
|
if (checkCloseFlag) {
|
|
return;
|
|
}
|
|
checkCloseFlag = true;
|
|
if (business.forceClose) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text(S.of(context).warning),
|
|
content: Text(
|
|
S.of(context).store_closed,
|
|
),
|
|
actions: <Widget>[
|
|
FlatButton(
|
|
child: Text(S.of(context).ok),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
showPrompt(0);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
});
|
|
} else {
|
|
DateTime now = DateTime.now();
|
|
if (!(now.hour >= business.todayOpen &&
|
|
now.hour <= business.todayClose)) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
// title: Text(
|
|
// S.of(context).warning,
|
|
// ),
|
|
content: Container(
|
|
height: 170.0,
|
|
child: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Container(
|
|
child: Icon(
|
|
Icons.add_shopping_cart,
|
|
size: 100.0,
|
|
color: Colors.black12,
|
|
),
|
|
),
|
|
Container(
|
|
child: Text(
|
|
S.of(context).closed,
|
|
style: TextStyle(
|
|
fontSize: 20.0,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
child: Text(
|
|
S.of(context).book_now_delivery_later_token(
|
|
business.todayOpen),
|
|
style: TextStyle(
|
|
color: Colors.black38,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
FlatButton(
|
|
child: Text(S.of(context).ok),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
showPrompt(0);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
});
|
|
} else {
|
|
showPrompt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void showPrompt(int idx) {
|
|
if (_prompts.length > idx) {
|
|
Map<String, dynamic> _prompt = _prompts[idx] as Map<String, dynamic>;
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text(_prompt['title']),
|
|
content: Text(_prompt['message']),
|
|
actions: [
|
|
FlatButton(
|
|
child: Text(S.of(context).ok),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
showPrompt(idx + 1);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
}
|
|
|
|
class _SliverAppBarDelegate2 extends SliverPersistentHeaderDelegate {
|
|
_SliverAppBarDelegate2({
|
|
@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(_SliverAppBarDelegate2 oldDelegate) {
|
|
return maxHeight != oldDelegate.maxHeight ||
|
|
minHeight != oldDelegate.minHeight ||
|
|
child != oldDelegate.child;
|
|
}
|
|
}
|