initial commit to gitea

This commit is contained in:
2022-03-10 00:47:26 -05:00
parent 808ffa3211
commit f504e213a0
93 changed files with 4394 additions and 1539 deletions

View File

@@ -0,0 +1,411 @@
import 'package:flutter/material.dart';
import '/models/group.dart';
import '/pages/buy_service.dart';
import '../../constants.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
class CreateOnlineStore1 extends StatefulWidget {
final int businessId;
const CreateOnlineStore1(this.businessId, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return CreateOnlineStore1State();
}
}
class CreateOnlineStore1State extends State<CreateOnlineStore1> {
TextEditingController groupNumberController = TextEditingController();
TextEditingController domainController = TextEditingController();
bool canSubmit = false;
List<dynamic> stores = [];
Map<String, dynamic> service;
dynamic selectedStore;
Group group;
String selectedDomain;
List<dynamic> domainResult = [];
@override
Widget build(BuildContext context) {
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 20, left: 16, right: 16, bottom: 10,),
child: Text(
S.of(context).open_online_store,
style: TextStyle(
fontSize: 28,
),
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 4, bottom: 4, left: 16.0, right: 16.0),
child: Text(
S.of(context)
.select_a_store,
style: TextStyle(
color: Colors.black45,
),
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 4, bottom: 4, left: 16.0, right: 16.0),
child: DropdownButton<dynamic>(
items: stores.map((e) => DropdownMenuItem<dynamic>(
value: e,
child: Text(e['name']),
)).toList(),
isExpanded: false,
hint: Text(S.of(context).select_a_store),
onChanged: (newValue) {
print('newValue $newValue');
setState(() {
selectedStore = newValue;
});
},
value: selectedStore,
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 20, bottom: 20),
child: Visibility(
child: getSearchDomainWidget(),
visible: selectedStore != null,
),
),
);
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: (){
Navigator.of(context).pop();
},
),
title: Text(S.of(context).open_online_store),
backgroundColor: Theme.of(context).primaryColor,
),
body: SingleChildScrollView(
child: col,
),
);
}
Widget getSearchDomainWidget() {
if (selectedStore == null) {
return SizedBox.shrink();
}
Column col = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
);
col.children.add(
Container(
padding: EdgeInsets.only(bottom: 4,),
child: Text(selectedStore == null ? '' : selectedStore['name'],
style: TextStyle(
fontSize: 28,
),
),
),
);
Map<String, dynamic> existing_web_svc;
if (selectedStore != null && (selectedStore['services'] as List).length > 0) {
for (Map<String, dynamic>svc in (selectedStore['services'] as List)) {
if (svc['code'] == Constants.WEB_MINISTORE_SERVICE) {
existing_web_svc = svc;
break;
}
}
}
if (existing_web_svc != null) {
col.children.add(
Container(
padding: EdgeInsets.only(top: 8.0),
child: Text(
'${existing_web_svc['description']}',
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 4.0),
child: Text(
S.of(context).expiration_date + ": "
+ Utils.utcDatetimeStringToLocalDatetimeString(context, existing_web_svc['expiration_date']),
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 4.0),
child: Text(
S.of(context).store + ": " + '${existing_web_svc['store']['name']}'
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 4.0),
child: Text(
S.of(context).domain_name + ": " + '${existing_web_svc['domain']}'
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 10.0),
child: ElevatedButton(
child: Text(S.of(context).renew_service_now),
onPressed: () {
Navigator.pushReplacement(context, MaterialPageRoute(
builder: (BuildContext context) =>
BuyService(group.id, Constants.WEB_MINISTORE_SERVICE,
domain: existing_web_svc['domain'],
sid: existing_web_svc['store']['id'],
),
));
},
),
),
);
} else {
col.children.add(
Container(
padding: EdgeInsets.only(top: 8.0),
child: Text(S.of(context).enter_desired_domain),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 0,),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextFormField(
controller: domainController,
decoration: new InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.black12,
),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
),
labelText: S.of(context).domains_separated_comma,
),
style: TextStyle(
fontSize: 18.0
),
autofocus: true,
validator: (String value) {
if (value.trim().isEmpty) {
return S.of(context).domains_separated_comma;
}
return null;
},
onChanged: (string) {
if (string.isNotEmpty) {
canSubmit = true;
} else {
canSubmit = false;
}
setState(() {});
},
),
),
Container(
padding: EdgeInsets.only(left: 8.0),
child: ElevatedButton(
child: Text(
S.of(context).check_domain_name,
),
onPressed: domainController.text.length > 0 ? checkDomainAvailability : null,
),
),
],
),
)
);
if (domainResult.length > 0) {
for (dynamic dr in domainResult) {
col.children.add(_getDomain(dr));
}
}
if (selectedDomain != null && service != null) {
col.children.add(
Container(
padding: EdgeInsets.only(top: 8.0),
child: Text(
service['description'],
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 8.0),
child: Text(
service['options'][0]['name'],
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 8.0),
child: Text(
'\$${service['options'][0]['price']}',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
),
);
col.children.add(
Container(
padding: EdgeInsets.only(top: 10.0),
child: ElevatedButton(
child: Text(
S.of(context).place_order_now,
),
onPressed: () {
Navigator.pushReplacement(context, MaterialPageRoute(
builder: (BuildContext context) =>
BuyService(group.id, Constants.WEB_MINISTORE_SERVICE,
domain: selectedDomain,
sid: selectedStore['id'],
),
));
},
),
),
);
}
}
return Container(
padding: EdgeInsets.only(bottom: 4,),
child: col,
);
}
@override
void initState() {
super.initState();
HttpUtil.httpGet(
'v1/get-contact-stores',
queryParameters: {
'service': Constants.WEB_MINISTORE_SERVICE
}
).then((value) {
setState(() {
stores = value['stores'];
service = value['service'];
group = Group.fromJson(value['group']);
print('stores: ${value}');
});
}).onError((error, stackTrace) {
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.navigateTo(context, '/');
});
});
}
Future<void> checkDomainAvailability() async {
Utils.showLoadingDialog(context);
selectedDomain = null;
domainResult = [];
setState(() {});
HttpUtil.httpGet(
'v1/check-domain-availability',
queryParameters: {
'domain': domainController.text,
},
).then((value) {
Routes.router.pop(context);
print('value: $value');
if (value['status'] == 'OK') {
domainResult = (value['domain_check_result'] as List);
setState(() {});
} else if (value['status'] == 'ERROR') {
String errors = '';
for (int i = 0; i < (value['errors'] as List).length; i++) {
errors += value['errors'][i] + '\n';
}
Utils.showMessageDialog(context, errors);
}
}).onError((error, stackTrace) {
Routes.router.pop(context);
Utils.showMessageDialog(context, error);
});
}
Widget _getDomain(dynamic d) {
if ((d['available'] as bool)) {
return Container(
child: ListTile(
title: Text(
d['domain'],
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
leading: Radio(
value: d['domain'],
groupValue: selectedDomain,
onChanged: (val) {
setState(() {
selectedDomain = val;
});
},
),
),
);
} else {
return Container(
padding: EdgeInsets.only(top: 1.0),
child: ListTile(
title: Text(
d['domain'],
style: TextStyle(
color: Colors.black45,
),
),
leading: Icon(Icons.clear, color: Colors.red,),
),
);
}
}
}

View File

@@ -52,6 +52,18 @@ class MobileBuyServiceState extends State<MobileBuyService> {
Utils.buildLine(S.of(context).your_group, widget.data['group']['name'], valueSize: 16),
);
if (widget.data['store']['id'] != null) {
col1.children.add(
Utils.buildLine(S.of(context).store, widget.data['store']['name'], valueSize: 16),
);
}
if (widget.data['domain'] != null) {
col1.children.add(
Utils.buildLine(S.of(context).domain_name, widget.data['domain'], valueSize: 16),
);
}
if (widget.data['exists_service'] == null) {
col1.children.add(
Utils.buildLine(S.of(context).current_plan, 'N/A', valueSize: 16),

View File

@@ -335,6 +335,7 @@ class MobileCheckoutState extends State<MobileCheckout> with SingleTickerProvide
check();
},
initialLabelIndex: deliveryMethodIndex,
totalSwitches: shippingMethodLabels.length,
);
switch (position) {
case 0:

View File

@@ -189,6 +189,7 @@ class MobileNavigationDrawerState extends State<MobileNavigationDrawer> {
TextLink(S.of(context).contact_us, '/contact-us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).about_us, '/about-us', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).renew_license, '/renew-license', paddingVertical: 5.0, paddingHorizontal: 10.0, closeDrawer: true,),
TextLink(S.of(context).create_a_online_store, '/contact-stores', paddingVertical: 5.0, paddingHorizontal: 10.0,),
Container(
margin: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Text(

View File

@@ -1,92 +0,0 @@
import '../../constants.dart';
import '../../utils/iframe_web.dart' if (dart.library.io) '../../utils/fake_iframe_web.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class MobileTutorials extends StatefulWidget {
const MobileTutorials({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return MobileTutorialsState();
}
}
class MobileTutorialsState extends State<MobileTutorials> {
InAppWebViewController webView;
String url = "";
double progress = 0;
bool isLoadStop = false;
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: IFrameWeb(
width: double.maxFinite.toString(),
height: double.maxFinite.toString(),
src: Constants.TUTORIAL_URL,
),
),
],
);
} else {
print('progress: $progress');
return Stack(
children: [
InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse(Constants.TUTORIAL_URL)),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (controller, url) {
if (mounted) {
setState(() {
this.url = url.toString();
this.isLoadStop = false;
});
}
},
onLoadStop: (controller, url) async {
if (mounted) {
setState(() {
this.url = url.toString();
this.isLoadStop = true;
});
}
},
onProgressChanged: (InAppWebViewController controller, int progress) {
if (mounted) {
setState(() {
if (this.progress < 1.0) {
this.isLoadStop = false;
} else {
this.isLoadStop = true;
}
this.progress = progress / 100;
});
}
},
),
isLoadStop ? Container() :
Center(
child: CircularProgressIndicator(),
),
],
);
}
}
}

View File

@@ -13,6 +13,8 @@ import '../../store/store.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../utils/util_io.dart' if (dart.library.html) '../../utils/util_web.dart';
class MobileViewBlog extends StatefulWidget {
final Key key;
final int bid;
@@ -126,11 +128,7 @@ class MobileViewBlogState extends State<MobileViewBlog> {
Container(
width: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) - 100.0,
height: min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) - 100.0,
child: PhotoView(
imageProvider: NetworkImage(
'https:${blog.imageUrl}',
),
),
child: Util.showImage('https:${blog.imageUrl}'),
) : SizedBox.shrink(),
decoration: BoxDecoration(
border: Border(

View File

@@ -0,0 +1,261 @@
import 'package:flutter/material.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/product.dart';
import '../../pages/product_detail_page.dart';
import '../../utils/util_io.dart' if (dart.library.html) '../../utils/util_web.dart';
import '../../widgets/general/add_remove_button.dart';
import '../../widgets/general/show_price.dart';
class ProductItem extends StatefulWidget {
final Business business;
final Product product;
final bool horizontal;
final double imageWidth;
const ProductItem(this.product, this.business, {
Key key,
this.horizontal = false,
this.imageWidth = 110,
}) : super(key: key);
@override
State<StatefulWidget> createState() => ProductItemState();
}
class ProductItemState extends State<ProductItem> {
@override
Widget build(BuildContext context) {
return Container(
// width: 160.0,
height: widget.horizontal ? Constants.PRODUCT_ITEM_HEIGHT_H : Constants.PRODUCT_ITEM_HEIGHT_V,
padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 15.0).copyWith(bottom: 5.0),
decoration: new BoxDecoration(
color: Colors.white,
border: new Border(
bottom: new BorderSide(
color: new Color(0xFFEBEBEB),
),
),
),
child: new SizedBox.expand(
child: Container(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 10.0),
child: widget.horizontal ? getHorizontal(true) : getVertial(true),
),
),
);
}
Widget getHorizontal(bool onHover) {
Row row1 = Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: new EdgeInsets.all(10.0),
width: widget.imageWidth,
height: widget.imageWidth,
child: GestureDetector(
child: onHover && widget.product.secondImagePath.isNotEmpty ?
Util.showImage('${widget.product.secondImagePath}',
fit: BoxFit.fill,
) :
Util.showImage('${widget.product.imagePath}',
fit: BoxFit.fill,
),
onTap: (){
_showProductDetail();
},
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: GestureDetector(
child: Text(
'${widget.product.name}',
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
maxLines: 2,
softWrap: true,
style: new TextStyle(
fontSize: 15.0,
),
),
onTap: (){
_showProductDetail();
},
),
),
Text(
widget.product.description,
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: new TextStyle(
fontSize: 12.0,
color: new Color(0xFF999999),
),
),
],
),
),
],
);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
row1,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
child: widget.business.showMonthlySold ?
Text(
S.of(context).sold_per_month_token(widget.product.monthSales.toStringAsFixed(0)),
style: new TextStyle(
fontSize: 9.0
),
) : SizedBox.shrink(),
),
ShowPrice(
widget.product.price,
regularPrice: widget.product.regularPrice,
currencySign: '\$',
fontWeight: FontWeight.bold,
),
],
),
AddRemoveButton(product: widget.product, business: widget.business, addOnly: false, onHovor: onHover,),
],
),
],
);
}
Widget getVertial(bool onHover) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
margin: new EdgeInsets.all(10.0),
width: widget.imageWidth,
height: widget.imageWidth,
child: GestureDetector(
child: onHover && widget.product.secondImagePath.isNotEmpty ?
Util.showImage('${widget.product.secondImagePath}',
fit: BoxFit.fill,
) :
Util.showImage('${widget.product.imagePath}',
fit: BoxFit.fill,
),
onTap: (){
_showProductDetail();
},
),
),
new Expanded(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: GestureDetector(
child: Text(
'${widget.product.name}',
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
maxLines: 2,
softWrap: true,
style: new TextStyle(
fontSize: 15.0,
),
),
onTap: (){
_showProductDetail();
},
),
),
Container(
child: Text(
widget.product.description,
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: new TextStyle(
fontSize: 12.0,
color: new Color(0xFF999999),
),
),
),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
child: widget.business.showMonthlySold ?
Text(
S.of(context).sold_per_month_token(widget.product.monthSales.toStringAsFixed(0)),
style: new TextStyle(
fontSize: 9.0
),
) : SizedBox.shrink(),
),
ShowPrice(
widget.product.price,
regularPrice: widget.product.regularPrice,
currencySign: '\$',
fontWeight: FontWeight.bold,
),
],
),
AddRemoveButton(product: widget.product, business: widget.business, addOnly: false, onHovor: onHover,),
],
),
],
),
),
],
);
}
void _showProductDetail() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
product: widget.product,
business: widget.business,
)),
);
}
@override
void initState() {
super.initState();
eventBus.on<OnCartInfoUpdated>().listen((event) {
if (mounted) {
setState(() {
});
}
});
}
}

View File

@@ -0,0 +1,152 @@
import 'package:dio/dio.dart';
import 'package:flappy_search_bar/flappy_search_bar.dart';
import 'package:flutter/material.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/product.dart';
import '../../pages/product_detail_page.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
import '../../widgets/general/breadcrumbs.dart';
import '../../widgets/general/navigationbar.dart';
import 'product_item.dart';
class ProductSearch extends StatefulWidget {
final Business business;
const ProductSearch(this.business, {Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => ProductSearchState();
}
class ProductSearchState extends State<ProductSearch> {
int page = 1;
int numPerPage = 10;
int lastResultSize = 0;
double lastBottomPosition = 0;
String lastKeyword = '';
SearchBarController _controller = SearchBarController<Product>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MiniNavigationBar(
title: S.of(context).search_product,
back: true,
breadCrumbs: [
BreadCrumb(S.of(context).search_product, null),
],
),
drawer: null,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: NotificationListener<ScrollNotification>(
child: SearchBar(
searchBarController: _controller,
minimumChars: 2,
hintText: S.of(context).enter_keyword,
cancellationWidget: Text(
S.of(context).cancel,
),
onSearch: search,
onItemFound: (Product searchProduct, int index) {
return ProductItem(
searchProduct,
widget.business,
horizontal: true,
imageWidth: 80.0,
);
},
onError: (error) {
return Center(
child: Text("$error"),
);
},
emptyWidget: Center(
child: Text(
S.of(context).empty_result_change_keyword,
),
),
),
onNotification: (notification) {
if (notification.metrics.atEdge) {
print('fff: $page, $lastBottomPosition, ${notification.metrics.pixels}');
if (page != 0 && lastResultSize >= numPerPage && notification.metrics.pixels != 0) {
if (notification.metrics.pixels > lastBottomPosition) {
lastBottomPosition = notification.metrics.pixels;
page += 1;
_controller.replayLastSearch();
}
}
}
return true;
},
),
),
),
bottomNavigationBar: null,
);
}
Future<List<Product>> search(String keyword) async {
if (lastKeyword != keyword) {
page = 1;
}
lastKeyword = keyword;
var result = await HttpUtil.httpGet('v1/search-store-product2',
queryParameters: {
'store_id': widget.business.id,
'keyword': keyword,
'page': page,
'num_per_page': numPerPage,
},
returnError: true,
);
if (result is DioError) {
if (result.response != null) {
throw RuntimeError(result.response.data);
} else {
throw RuntimeError(result.message);
}
} else if (result != null && result is List) {
lastBottomPosition = 0;
var r = result.map((e) => Product.fromJson(e)).toList();
lastResultSize = r.length;
if (lastResultSize < numPerPage) {
page = 1;
}
return r;
}
return [];
}
@override
void initState() {
super.initState();
eventBus.on<OnCartInfoUpdated>().listen((event) {
if (mounted) {
setState(() {});
}
});
}
void _showProductDetail(Product p) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
product: p,
business: widget.business,
)),
);
}
}

View File

@@ -1,63 +1,59 @@
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/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:fluttertoast/fluttertoast.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 '../../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 '../../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';
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>
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;
@@ -75,7 +71,7 @@ class MobileShopState extends State<MobileShop>
static const num _categoryHeight = 50.0;
static const num _categoryDescHeight = 50.0;
static const num _productHeight = 115.0;
static const num _productHeight = Constants.PRODUCT_ITEM_HEIGHT_H;
int _categoryIndex = 0;
bool _categoryIndexChange = false;
@@ -103,7 +99,7 @@ class MobileShopState extends State<MobileShop>
int _commentPageCount = 1;
bool _commentLoadingFinish = false;
RefreshController _commentRefreshController =
RefreshController(initialRefresh: true);
RefreshController(initialRefresh: true);
PanelController panelController = PanelController();
SlidingUpPanel _slidUpShoppingCart;
@@ -256,9 +252,33 @@ class MobileShopState extends State<MobileShop>
padding: EdgeInsets.only(
left: 10.0,
top: 10.0,
right: 10.0,
bottom: 10.0,
),
width: 150.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,
@@ -267,6 +287,7 @@ class MobileShopState extends State<MobileShop>
child: Container(
child: Util.showImage(
_business.promoProducts[i].imagePath,
width: 110.0,
),
),
onTap: () {
@@ -308,7 +329,7 @@ class MobileShopState extends State<MobileShop>
pinned: false,
floating: true,
delegate: _SliverAppBarDelegate2(
minHeight: 250.0,
minHeight: 256.0,
maxHeight: 260.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@@ -359,7 +380,7 @@ class MobileShopState extends State<MobileShop>
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
if (_animationFinish) {
Routes.router.pop(context);
Navigator.of(context).maybePop();
}
}),
titleSpacing: 0.0,
@@ -371,8 +392,8 @@ class MobileShopState extends State<MobileShop>
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return StoreProductSearch(_business);
}));
return ProductSearch(_business);
}));
},
),
),
@@ -540,7 +561,7 @@ class MobileShopState extends State<MobileShop>
cancelButton: CupertinoActionSheetAction(
child: Text(S.of(context).close),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).maybePop();
},
),
);
@@ -786,8 +807,8 @@ class MobileShopState extends State<MobileShop>
));
var duration = Duration(
seconds: (_business.distanceInfo.duration != null
? _business.distanceInfo.duration.value
: 30) +
? _business.distanceInfo.duration.value
: 30) +
_business.shippingTime * 60);
var hours = duration.inHours.remainder(60);
var minutes = duration.inMinutes.remainder(60);
@@ -922,28 +943,28 @@ class MobileShopState extends State<MobileShop>
children: <Widget>[
_business.bulletin.isNotEmpty ? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
children: <Widget>[
Container(
padding:
EdgeInsets.only(left: 10.0, right: 5.0, bottom: 10.0),
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)),
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(),
),
],
) : SizedBox.shrink(),
_business.description.isNotEmpty ? Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -1092,13 +1113,13 @@ class MobileShopState extends State<MobileShop>
}
Widget _buildMainContent() {
return new Row(
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
_buildCategories(),
Expanded(
child: _buildProducts(),
)
),
],
);
}
@@ -1116,7 +1137,7 @@ class MobileShopState extends State<MobileShop>
CategoryProducts cp = _categoryProducts[i];
int qtyInCategory = 0;
CartInfo cartInfo =
Utils.getCartInfoByBusiness(store.state.cartInfos, _business);
Utils.getCartInfoByBusiness(store.state.cartInfos, _business);
if (cartInfo != null &&
cartInfo.businessInfo.id == _business.id &&
cartInfo.productList != null) {
@@ -1141,14 +1162,14 @@ class MobileShopState extends State<MobileShop>
style: TextStyle(
fontSize: 13.0,
fontWeight: (cp.id == Constants.FEATURED_PRODUCT_ID ||
cp.id == Constants.HOT_SALE_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)),
? Colors.lightGreen
: Colors.black87)),
// overflow: kIsWeb ? null : TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
softWrap: true,
@@ -1178,10 +1199,10 @@ class MobileShopState extends State<MobileShop>
height: 70.0,
padding: new EdgeInsets.symmetric(horizontal: 10.0),
decoration: new BoxDecoration(
color: _categoryIndex == i ? Style.backgroundColor : null,
color: _categoryIndex == i ? Colors.white : null,
border: new Border(
bottom:
new BorderSide(color: new Color(0xFFEBEBEB)))),
new BorderSide(color: new Color(0xFFEBEBEB)))),
child: categoryRow,
),
onTap: () => _selectCategory(i),
@@ -1208,10 +1229,10 @@ class MobileShopState extends State<MobileShop>
_categoryIndexChange = true;
_listScrollController1
.animateTo(height,
duration: new Duration(
microseconds: 200,
),
curve: Curves.linear)
duration: new Duration(
microseconds: 200,
),
curve: Curves.linear)
.then((value) {
_categoryIndexChange = false;
});
@@ -1316,7 +1337,7 @@ class MobileShopState extends State<MobileShop>
controller: _listScrollController1,
// itemCount: displayProductByCategoryClick ? 1 : (_categoryProducts == null ? 0 : _categoryProducts.length),
itemCount:
_categoryProducts == null ? 0 : numCategoriesHasProducts(),
_categoryProducts == null ? 0 : numCategoriesHasProducts(),
itemBuilder: (BuildContext context, int i) {
CategoryProducts cp;
cp = _categoryProducts[i];
@@ -1357,42 +1378,42 @@ class MobileShopState extends State<MobileShop>
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:
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)
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)
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)),
overflow: TextOverflow.ellipsis,
),
),
replacement: const SizedBox.shrink()),
],
))
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) {
@@ -1400,9 +1421,13 @@ class MobileShopState extends State<MobileShop>
children: <Widget>[],
);
pStack.children.add(new ProductItem(
product: p,
business: _business,
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);
@@ -1494,129 +1519,6 @@ class MobileShopState extends State<MobileShop>
}
});
// 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();
}
@@ -1630,67 +1532,71 @@ class MobileShopState extends State<MobileShop>
}
_productCurrentPage = 1;
Utils.loadProducts(
widget.businessId, categoryId, _productCurrentPage, false,
(value) {
if (_isLoading) {
_isLoading = false;
Navigator.of(context).pop();
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);
}
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);
}
);
}
@@ -1700,59 +1606,59 @@ class MobileShopState extends State<MobileShop>
}
_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) {
(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 {
displayProductByCategoryClickIndicator =
S.of(context).pull_up_to_load_more;
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;
}
}
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);
}
},
(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}) {
double price,
Product product,
String name,
String description,
double quantity}) {
CartLineItem lineItem = CartLineItem();
lineItem.unitPrice = price;
lineItem.product = product;
@@ -1787,9 +1693,9 @@ class MobileShopState extends State<MobileShop>
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
product: p,
business: _business,
)),
product: p,
business: _business,
)),
);
}
@@ -1925,10 +1831,10 @@ class MobileShopState extends State<MobileShop>
S.of(context).store_closed,
),
actions: <Widget>[
FlatButton(
TextButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).maybePop();
showPrompt(0);
},
)
@@ -1981,10 +1887,10 @@ class MobileShopState extends State<MobileShop>
),
),
actions: <Widget>[
FlatButton(
TextButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).maybePop();
showPrompt(0);
},
)
@@ -2007,10 +1913,10 @@ class MobileShopState extends State<MobileShop>
title: Text(_prompt['title']),
content: Text(_prompt['message']),
actions: [
FlatButton(
TextButton(
child: Text(S.of(context).ok),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).maybePop();
showPrompt(idx + 1);
},
),
@@ -2053,4 +1959,4 @@ class _SliverAppBarDelegate2 extends SliverPersistentHeaderDelegate {
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
}