Files
flutter_wisetronic/lib/widgets/mobile/shop.dart
2022-06-30 03:47:47 -04:00

1962 lines
64 KiB
Dart

import 'dart:math';
import 'package:badges/badges.dart';
import 'package:flutter/cupertino.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:flutter_wisetronic/generated/l10n.dart';
import 'package:flutter_wisetronic/models/business.dart';
import 'package:flutter_wisetronic/models/cart_info.dart';
import 'package:flutter_wisetronic/models/cart_line_item.dart';
import 'package:flutter_wisetronic/models/category_products.dart';
import 'package:flutter_wisetronic/models/comment.dart';
import 'package:flutter_wisetronic/models/product.dart';
import 'package:flutter_wisetronic/models/product_image.dart';
import 'package:flutter_wisetronic/pages/product_detail_page.dart';
import 'package:flutter_wisetronic/store/store.dart';
import 'package:flutter_wisetronic/utils/http_util.dart';
import 'package:flutter_wisetronic/utils/shop_scroll_controller.dart';
import 'package:flutter_wisetronic/utils/shop_scroll_coordinator.dart';
import 'package:flutter_wisetronic/utils/utils.dart';
import 'package:flutter_wisetronic/widgets/general/add_remove_button.dart';
import 'package:flutter_wisetronic/widgets/general/animation_point_manager.dart';
import 'package:flutter_wisetronic/widgets/general/carousel.dart';
import 'package:flutter_wisetronic/widgets/general/read_more_text.dart';
import 'package:flutter_wisetronic/widgets/general/show_price.dart';
import 'package:flutter_wisetronic/widgets/general/sliding_up_panel.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:smooth_star_rating/smooth_star_rating.dart';
import '../../constants.dart';
import '../../utils/util_io.dart' if (dart.library.html) '../../utils/util_web.dart';
import 'product_item.dart';
import 'product_search.dart';
import 'shopping_cart_bar.dart';
MediaQueryData mediaQuery;
double statusBarHeight;
double screenWidth;
double screenHeight;
class Shop extends StatefulWidget {
final int businessId;
const Shop({Key key, this.businessId}) : super(key: key);
@override
State<StatefulWidget> createState() => ShopState();
}
class ShopState extends State<Shop>
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 = Constants.PRODUCT_ITEM_HEIGHT_H;
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,
right: 10.0,
bottom: 10.0,
),
margin: EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Colors.white,
border: Border(
top: BorderSide(
width: 1.0,
color: Colors.black12,
),
bottom: BorderSide(
width: 1.0,
color: Colors.black12,
),
left: BorderSide(
width: 1.0,
color: Colors.black12,
),
right: BorderSide(
width: 1.0,
color: Colors.black12,
),
),
),
width: 160.0,
height: 220.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
GestureDetector(
child: Container(
child: Util.showImage(
_business.promoProducts[i].imagePath,
width: 110.0,
),
),
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: 256.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) {
Navigator.of(context).maybePop();
}
}),
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 ProductSearch(_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: () {
ScaffoldMessenger.of(context).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).maybePop();
},
),
);
}
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 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 ? Colors.white : 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(Container(
// width: MediaQuery.of(context).size.width - 100.0,
child: ProductItem(
p, _business,
horizontal: true,
imageWidth: 80.0,
),
));
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;
});
}
});
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();
_featuredProducts = [];
// _hotSaleProducts = (value['hot_sale_products'] as List)
// .map((i) => Product.fromJson(i))
// .toList();
_hotSaleProducts = [];
_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).maybePop();
}
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).maybePop();
}
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).maybePop();
}
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>[
TextButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).maybePop();
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>[
TextButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).maybePop();
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: [
TextButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).maybePop();
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;
}
}