backup. before shop update

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

View File

@@ -0,0 +1,395 @@
import 'package:badges/badges.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../generated/l10n.dart';
import '../../models/business.dart';
import '../../models/cart_info.dart';
import '../../models/product.dart';
import '../../pages/attribute_selection.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/utils.dart';
class AddRemoveButton extends StatefulWidget {
final Product product;
final Business business;
final bool addOnly;
final int cartLineItemIndex;
final bool addToBasket;
AddRemoveButton({
this.product,
this.business,
this.addOnly = false,
this.cartLineItemIndex = -1,
this.addToBasket = false,
});
@override
State<StatefulWidget> createState() {
return new AddRemoveButtonState();
}
}
class AddRemoveButtonState extends State<AddRemoveButton> {
int _qty;
var zeroColor = const Color(0xFFEFEFEF);
var qtyColor = const Color(0xFFFF6666);
var zeroFontColor = const Color(0xFF888888);
var qtyFontColor = const Color(0xFFFFFFFF);
var d = 1;
CartInfo cartInfo;
GlobalKey startKey = GlobalKey();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (widget.product.leftNum == null) {
_qty = 0;
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
if (cartInfo != null) {
for (var i = 0; i < cartInfo.productList.length; i++) {
if (cartInfo.productList[i].product.id == widget.product.id
&& cartInfo.productList[i].unitPrice == 0.0) {
_qty = cartInfo.productList[i].quantity.round();
break;
}
}
}
return Container(
padding: EdgeInsets.only(top: 5.0, bottom: 5.0, left: 32.0, right: 32.0),
child: Text(
'x$_qty'
),
);
}
if (widget.product.leftNum <= 0) {
return Container(
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 8.0, right: 8.0),
child: Text(
S.of(context).out_of_stock,
style: TextStyle(
fontSize: 8.0,
color: Colors.white,
),
),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(3.0),
topRight: Radius.circular(3.0),
bottomLeft: Radius.circular(3.0),
bottomRight: Radius.circular(3.0),
),
),
);
}
if (widget.addToBasket) {
_qty = 0;
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
if (cartInfo != null) {
for (var i = 0; i < cartInfo.productList.length; i++) {
if (cartInfo.productList[i].product.id == widget.product.id) {
_qty = cartInfo.productList[i].quantity.round();
break;
}
}
}
if (_qty > 0) {
return Badge(
badgeContent: Text(
'$_qty',
style: TextStyle(
color: Colors.white,
fontSize: 17.0,
),
),
padding: EdgeInsets.all(10),
position: BadgePosition.topEnd(top: -15, end: -10),
badgeColor: Colors.lightBlueAccent,
child: RaisedButton.icon(
padding: EdgeInsets.only(left: 32, right: 32, top: 20, bottom: 20),
elevation: 2.0,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
),
color: Colors.redAccent,
icon: Icon(
Icons.shopping_basket_outlined,
size: 32.0,
color: Colors.yellow,
),
label: Text(
S.of(context).add_to_basket,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
onPressed: () {
_addToCart(context);
},
),
);
} else {
return RaisedButton.icon(
padding: EdgeInsets.only(left: 32, right: 32, top: 20, bottom: 20),
elevation: 2.0,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
),
color: Colors.redAccent,
icon: Icon(
Icons.shopping_basket_outlined,
size: 32.0,
color: Colors.yellow,
),
label: Text(
S.of(context).add_to_basket,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
onPressed: () {
_addToCart(context);
},
);
}
}else if (widget.addOnly) {
return Container(
key: startKey,
padding: EdgeInsets.all(0.0),
child: GestureDetector(
child: Icon(
Icons.add_circle,
size: 24.0,
),
onTap: () {
_addToCart(context);
},
),
);
} else {
_qty = 0;
cartInfo = Utils.getCartInfoByBusiness(store.state.cartInfos, widget.business);
if (cartInfo != null) {
for (var i = 0; i < cartInfo.productList.length; i++) {
if (cartInfo.productList[i].product.id == widget.product.id) {
_qty = cartInfo.productList[i].quantity.round();
break;
}
}
}
if (widget.product.productAttributes != null &&
widget.product.productAttributes.length > 0 && widget.cartLineItemIndex == -1) {
return new Row(
key: startKey,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new GestureDetector(
child: new Container(
width: 60.0,
child: new Center(
child: new Text(
'$_qty',
style: new TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.bold,
color: _qty > 0 ? qtyFontColor : zeroFontColor
),
),
),
padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0),
decoration: BoxDecoration(
color: _qty > 0 ? qtyColor : zeroColor,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
bottomRight: Radius.circular(10.0),
),
),
),
onTap: () {
_addToCart(context);
},
),
],
);
} else {
return new Row(
key: startKey,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
new GestureDetector(
child: new Container(
width: 30.0,
child: new Center(
child: new Text(
'$_qty',
style: new TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.bold,
color: _qty > 0 ? qtyFontColor : zeroFontColor
),
),
),
padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0),
decoration: BoxDecoration(
color: _qty > 0 ? qtyColor : zeroColor,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
),
),
),
onTap: () {
_addToCart(context);
},
),
new GestureDetector(
child: new Container(
width: 30.0,
child: new Center(
child: new Text(
'-',
style: new TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.bold,
),
),
),
padding: EdgeInsets.all(5.0).copyWith(left: 10.0, right: 10.0),
decoration: BoxDecoration(
color: const Color(0xFFABABAB),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.only(
topRight: Radius.circular(10.0),
bottomRight: Radius.circular(10.0),
),
),
),
onTap: () {
if (_qty > 0) {
_removeFromCart(context);
}
},
),
],
);
}
}
}
void _addToCart(BuildContext context) {
if (widget.cartLineItemIndex != -1) {
if (cartInfo.productList[widget.cartLineItemIndex].quantity + 1.0 > widget.product.leftNum) {
Fluttertoast.showToast(
msg: S.of(context).product_insufficient,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.red,
textColor: Colors.white
);
} else {
cartInfo.productList[widget.cartLineItemIndex].quantity += 1.0;
Utils.addSubproductQty(cartInfo, cartInfo.productList[widget.cartLineItemIndex]);
store.dispatch(UpdateCartInfo(
Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
eventBus.fire(new OnCartInfoUpdated());
}
} else {
if (widget.product.productAttributes.length > 0) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
new AttributeSelection(
product: widget.product, business: widget.business, startKey: startKey,)),
);
} else {
eventBus.fire(new OnProductWillAddToCart(widget.product, {},
widget.product.price, widget.product.description,
widget.business, buttonKey: startKey));
}
}
}
void _removeFromCart(BuildContext context) {
if (widget.cartLineItemIndex != -1) {
if (cartInfo.productList[widget.cartLineItemIndex].quantity <= 1) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).warning),
content: Text(S.of(context).are_you_sure_to_remove_the_item),
actions: <Widget>[
FlatButton(
child: Text(S.of(context).cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text(S.of(context).yes_i_am_sure),
onPressed: () {
_removeCartLineItem();
Navigator.of(context).pop();
},
),
],
);
}
);
} else {
_removeCartLineItem();
}
} else {
eventBus.fire(new OnProductWillRemoveFromCart(
widget.product, -1, widget.business));
}
}
void _removeCartLineItem() {
if (cartInfo.productList[widget.cartLineItemIndex].quantity <= 1) {
String uuid = cartInfo.productList[widget.cartLineItemIndex].uuid;
cartInfo.productList.removeAt(widget.cartLineItemIndex);
Utils.removeSubproduct(cartInfo, uuid);
} else {
cartInfo.productList[widget.cartLineItemIndex].quantity -= 1;
Utils.addSubproductQty(cartInfo, cartInfo.productList[widget.cartLineItemIndex], remove: true);
}
if (cartInfo.productList.length <= 0) {
store.dispatch(new UpdateCartInfo(Utils.removeCartInfoFromCartInfoList(store.state.cartInfos, cartInfo)));
} else {
store.dispatch(new UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
}
eventBus.fire(new OnCartInfoUpdated());
}
@override
void setState(VoidCallback fn) {
if(mounted) {
super.setState(fn);
}
}
}

View File

@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'parabolic_animation_widget.dart';
import 'popup_animation_widget.dart';
class AnimationPointManager {
List<AnimatedWidget> list = [];
static AnimationController controller1;
static AnimationController controller2;
Future<void> addParabolicAniamtion({
@required TickerProvider vsync,
@required GlobalKey stackKey,
@required GlobalKey startKey,
@required GlobalKey endKey,
@required Duration duration,
@required AnimationStatusListener statusListener,
Color color = Colors.red,
double size = 20,
Offset startAdjustOffset = Offset.zero,
Offset endAdjustOffset = Offset.zero,
}) async {
controller1 = createController(vsync, duration);
Animation animation = createAnimation(controller1);
AnimatedWidget animatedWidget = ParabolicAnimationWidget(
animation: animation,
stackKey: stackKey,
startKey: startKey,
endKey: endKey,
size: size,
color: color,
startAdjustOffset: startAdjustOffset,
endAdjustOffset: endAdjustOffset,
);
list.add(animatedWidget);
statusListener(AnimationStatus.dismissed);
try {
await controller1.forward().orCancel;
list.remove(animatedWidget);
controller1.dispose();
print('Controller1 disposed');
} on TickerCanceled {
print("Ticker Canceled");
} catch (error) {
print('Error: $error');
}
statusListener(AnimationStatus.completed);
}
Future<void> addPopupAniamtion({
@required TickerProvider vsync,
@required GlobalKey stackKey,
@required GlobalKey startKey,
@required Widget child,
Duration duration,
Offset popupOffset = Offset.zero,
AnimationStatusListener statusListener,
}) async {
controller2 = createController(vsync, duration);
AnimatedWidget animatedWidget = PopupAnimationWidget(
animation: controller2.view,
stackKey: stackKey,
startKey: startKey,
child: child,
popupOffset: popupOffset,
);
list.add(animatedWidget);
statusListener(AnimationStatus.dismissed);
try {
await controller2.forward().orCancel;
await controller2.reverse().orCancel;
list.remove(animatedWidget);
controller2.dispose();
print('Controller2 disposed');
} on TickerCanceled {
print("Ticker Canceled");
} catch (error) {
print('Error: $error');
}
statusListener(AnimationStatus.completed);
}
static AnimationController createController(
TickerProvider vsync, Duration duration) {
AnimationController ani = AnimationController(
lowerBound: 0,
upperBound: 1,
duration: duration ?? Duration(milliseconds: 800),
vsync: vsync);
return ani;
}
static CurvedAnimation createAnimation(controller) {
return CurvedAnimation(parent: controller, curve: Curves.linear);
}
void dispose() {
try {
controller1?.dispose();
controller2?.dispose();
} catch (error) {
}
}
}

View File

@@ -0,0 +1,349 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import '../../../events/eventbus.dart';
import '../../../events/events.dart';
import '../../../generated/l10n.dart';
import '../../../models/product.dart';
import '../../../models/product_option.dart';
import '../../../utils/utils.dart';
import 'rules.dart';
import 'options_base.dart';
class CheckOptions extends OptionsBase {
CheckOptions({@required Product product, @required int index, @required Map<String, dynamic> selections})
: super(product: product, index: index, selections: selections);
@override
State<StatefulWidget> createState() {
return new CheckOptionsState();
}
}
class CheckOptionsState extends OptionsBaseState<CheckOptions> {
Map<String, dynamic> optionsState = new Map<String, dynamic>();
int thisLimitQty = 0;
@override
Widget build(BuildContext context) {
Widget w = Center(
child: Text('Error'),
);
try {
w = _getOptionWidget();
} catch (error, stacktrace) {
print('Error: $error, Trace: $stacktrace');
}
return new ListView.builder(
itemCount: 2,
itemBuilder: (context, index) {
if (index == 0) {
return new Container(
padding: new EdgeInsets.all(10.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
S.of(context).check_option_select_token(product.productAttributes[this.index].name),
style: new TextStyle(
fontSize: 12.5,
),
overflow: TextOverflow.ellipsis,
),
new Text(
product.productAttributes[this.index].required ? S.of(context).check_option_is_required : S.of(context).check_option_is_optional,
style: new TextStyle(
fontSize: 10.0,
color: new Color(0xFF999999)
),
overflow: TextOverflow.ellipsis,
)
],
),
);
}
return w;
}
);
}
@override
void initState() {
super.initState();
setState(() {
product = widget.product;
selections = widget.selections;
index = widget.index;
});
eventBus.on<OnAttributePageChanged>().listen((event) {
setState(() {
index = event.index;
});
});
}
void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) {
Map<String, dynamic> opt = {
'name': name,
'quantity': quantity,
'adjust_amount': adjustAmount
};
var cloneSelections = json.decode(json.encode(selections));
int idx = Utils.selectionsContains(cloneSelections, product.productAttributes[index].name, name);
if (idx != -1) {
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).removeAt(idx);
} else if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())) {
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).add(opt);
} else {
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
}
if (idx != -1 && (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).length == 0) {
cloneSelections.remove(product.productAttributes[index].name.toUpperCase());
}
setOptionsStateDisabled(product.productAttributes[index].name, false);
setState(() {
selections = cloneSelections;
});
eventBus.fire(new OnAttributeSelectionsChanged(selections));
}
Widget _getOptionWidget() {
var row = Wrap(
children: <Widget>[],
);
List<ProductOption> productOptions = product.productAttributes[index].productOptions;
if (!optionsState.containsKey(product.productAttributes[index].name)) {
List<Map<String, dynamic>> optionState = [];
for (var i = 0; i < productOptions.length; i++) {
optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'check': false});
}
optionsState[product.productAttributes[index].name] = optionState;
}
if (selections.containsKey(product.productAttributes[index].name.toUpperCase())) {
Map<String, dynamic> attrExtraJson = Utils.stringToJson(
product.productAttributes[index].extra);
if (attrExtraJson != null) {
var selectLimitIfFieldEqualsTo = Rule.getRule(
attrExtraJson, Rule.RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO);
if (selectLimitIfFieldEqualsTo != null) {
if (selectLimitIfFieldEqualsTo is List) {
for (var b = 0; b < (selectLimitIfFieldEqualsTo as List).length; b++) {
Map<String, dynamic> selectLimitIfFieldEqualsTo1 = selectLimitIfFieldEqualsTo[b];
if (selectLimitIfFieldEqualsTo1.containsKey(
Rule.RULE_KEY_FORCE_LIMITED)) {
int limitQty = selectLimitIfFieldEqualsTo1[Rule
.RULE_KEY_FORCE_LIMITED];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
if (selectLimitIfFieldEqualsTo1.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0])) {
int limitQty = selectLimitIfFieldEqualsTo1[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0]];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
}
}
}
} else {
if (selectLimitIfFieldEqualsTo.containsKey(
Rule.RULE_KEY_FORCE_LIMITED)) {
int limitQty = selectLimitIfFieldEqualsTo[Rule
.RULE_KEY_FORCE_LIMITED];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
if (selectLimitIfFieldEqualsTo.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0])) {
int limitQty = selectLimitIfFieldEqualsTo[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0]];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
}
}
}
}
}
for (var i = 0; i < productOptions.length; i++) {
Map<String, dynamic> extraJson = Utils.stringToJson(
productOptions[i].extra);
if (extraJson != null) {
Map<String, dynamic> exclusiveRule = Rule.getRule(
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
Map<String, dynamic> multiItemRule = Rule.getRule(extraJson, Rule.RULE_ACTUAL_QTY_IS);
if (exclusiveRule != null) {
if (thisLimitQty > 0 && !_checkOptionIsCheck(productOptions[i].name)) {
optionsState[product.productAttributes[index].name][i]['disabled'] = true;
} else {
if (_checkOptionIsCheck(productOptions[i].name)) {
setOptionsStateDisabled(
product.productAttributes[index].name, true);
optionsState[product.productAttributes[index]
.name][i]['disabled'] = false;
break;
}
}
}
if (multiItemRule != null) {
if (_checkOptionIsCheck(productOptions[i].name)) {
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
disableOptionIfNotSelected();
}
} else {
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
optionsState[product.productAttributes[index]
.name][i]['disabled'] = true;
}
}
}
}
}
}
List<Map<String, dynamic>> optionState = optionsState[product.productAttributes[index].name];
for (var i = 0; i < optionState.length; i++) {
Widget optionWidget = _getOptionCheck(
product.productAttributes[index].productOptions[i], optionState[i]['disabled'], i);
row.children.add(optionWidget);
}
return row;
}
void disableOptionIfNotSelected() {
setOptionsStateDisabled(product.productAttributes[index].name, true);
for (var i = 0; i < optionsState[product.productAttributes[index].name].length; i++) {
if (Utils.selectionsContains(selections, product.productAttributes[index].name, optionsState[product.productAttributes[index].name][i]['name']) != -1) {
optionsState[product.productAttributes[index].name][i]['disabled'] = false;
}
}
}
bool _checkOptionIsCheck(String name) {
if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) {
return true;
}
return false;
}
Widget _getOptionCheck(ProductOption productOption, bool optDisabled, int optIndex) {
bool disabled = optDisabled;
bool check = _checkOptionIsCheck(productOption.name);
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
// Utils.jsonPrettyPrint(extraJson);
double extraAdjustAmount = 0.0;
if (extraJson != null) {
var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE);
if (sizeBaseAdjustment != null) {
if (sizeBaseAdjustment is List) {
for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) {
Map<String, dynamic> sizeBaseAdjustment1 = sizeBaseAdjustment[i];
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) {
extraAdjustAmount += sizeBaseAdjustment1[attValues[0]];
}
}
} else {
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) {
extraAdjustAmount += sizeBaseAdjustment[attValues[0]];
}
}
}
var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO);
if (disabledRule != null) {
if (disabledRule is List) {
for (var i = 0; i < (disabledRule as List).length; i++) {
Map<String, dynamic> disabledRule1 = disabledRule[i];
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) {
disabled = true;
}
}
} else {
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) {
disabled = true;
}
}
}
}
return new GestureDetector(
child: new Container(
width: 100.0,
height: 70.0,
decoration: BoxDecoration(
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
shape: BoxShape.rectangle,
border: new Border.all(
color: check ? selectedBorderColor : deselectBorderColor,
width: 1.0,
),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
padding: new EdgeInsets.all(4.0),
margin: new EdgeInsets.all(10.0).copyWith(right: 0.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
productOption.name,
style: new TextStyle(
fontSize: 14.0,
color: check ? selectedTextColor : deselectTextColor,
),
maxLines: 2,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
new Text(
(productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '',
style: new TextStyle(
fontSize: 11.0,
color: check ? selectedTextColor : new Color(0xFFABABAB),
),
overflow: TextOverflow.ellipsis,
)
],
),
),
onTap: () => disabled ? null : _onOptionTappedCallback(
productOption.name, 0, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
);
}
void setOptionsStateDisabled(String attrName, bool disabled) {
if (optionsState.containsKey(attrName)) {
List<Map<String, dynamic>> optionState = optionsState[attrName];
for (var i = 0; i < optionState.length; i++) {
optionState[i]['disabled'] = disabled;
}
}
}
}

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/models/product.dart';
typedef void OnOptionTapped(String name, int quantity, double adjustAmount);
abstract class OptionsBase extends StatefulWidget {
final Product product;
final int index;
final Map<String, dynamic> selections;
OptionsBase({@required Product product, @required int index, @required Map<String, dynamic> selections})
: product = product,
index = index,
selections = selections;
}
abstract class OptionsBaseState<Base extends OptionsBase> extends State<Base> {
Product product;
Map<String, dynamic> selections;
int index;
final Color disabledBackgroundColor = new Color(0xFFBCBCBC);
final Color deselectBackgroundColor = new Color(0x00000000);
final Color deselectTextColor = new Color(0xFF333333);
final Color deselectBorderColor = new Color(0xFF888888);
final Color selectedBackgroundColor = new Color(0xFFFF8908);
final Color selectedTextColor = new Color(0xFFFFFFFF);
final Color selectedBorderColor = new Color(0xFF444444);
@override
void setState(VoidCallback fn) {
if(mounted) {
super.setState(fn);
}
}
}

View File

@@ -0,0 +1,422 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import '../../../events/eventbus.dart';
import '../../../events/events.dart';
import '../../../generated/l10n.dart';
import '../../../models/product.dart';
import '../../../models/product_option.dart';
import '../../../utils/utils.dart';
import 'options_base.dart';
import 'rules.dart';
class QtyOptions extends OptionsBase {
QtyOptions({@required Product product, @required int index, @required Map<String, dynamic> selections})
: super(product: product, index: index, selections: selections);
@override
State<StatefulWidget> createState() {
return new QtyOptionsState();
}
}
class QtyOptionsState extends OptionsBaseState<QtyOptions> {
Map<String, dynamic> optionsState = new Map<String, dynamic>();
int thisLimitQty = 0;
@override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: 2,
itemBuilder: (context, index) {
if (index == 0) {
return new Container(
padding: new EdgeInsets.all(10.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
S.of(context).check_option_select_token(product.productAttributes[this.index].name),
style: new TextStyle(
fontSize: 12.5,
),
overflow: TextOverflow.ellipsis,
),
new Text(
product.productAttributes[this.index].required ? S.of(context).check_option_is_required : S.of(context).check_option_is_optional,
style: new TextStyle(
fontSize: 10.0,
color: new Color(0xFF999999)
),
overflow: TextOverflow.ellipsis,
)
],
),
);
}
return _getOptionWidget();
}
);
}
@override
void initState() {
super.initState();
setState(() {
product = widget.product;
selections = widget.selections;
index = widget.index;
});
eventBus.on<OnAttributePageChanged>().listen((event) {
setState(() {
index = event.index;
});
});
}
void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) {
Map<String, dynamic> opt = {
'name': name,
'quantity': quantity,
'adjust_amount': adjustAmount
};
var cloneSelections = json.decode(json.encode(selections));
int idx = Utils.selectionsContains(cloneSelections, product.productAttributes[index].name, name);
if (idx != -1) {
if (quantity == 1) {
cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] += 1;
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'];
} else {
if (cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] - 1 > 0) {
cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'] -= 1;
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = cloneSelections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'];
} else {
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).removeAt(idx);
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 0;
}
}
} else if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())) {
(cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).add(opt);
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 1;
} else {
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
optionsState[product.productAttributes[index].name][optIndex]['quantity'] = 1;
}
if (idx != -1 && (cloneSelections[product.productAttributes[index].name.toUpperCase()] as List).length == 0) {
cloneSelections.remove(product.productAttributes[index].name.toUpperCase());
}
setOptionsStateDisabled(product.productAttributes[index].name, false);
setState(() {
selections = cloneSelections;
});
eventBus.fire(new OnAttributeSelectionsChanged(selections));
}
Widget _getOptionWidget() {
var row = Wrap(
children: <Widget>[],
);
List<ProductOption> productOptions = product.productAttributes[index].productOptions;
if (!optionsState.containsKey(product.productAttributes[index].name)) {
List<Map<String, dynamic>> optionState = [];
for (var i = 0; i < productOptions.length; i++) {
int qty = 0;
int idx = Utils.selectionsContains(selections, product.productAttributes[index].name, productOptions[i].name);
if (idx != -1) {
qty = selections[product.productAttributes[index].name.toUpperCase()][idx]['quantity'];
}
optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'quantity': qty, 'check': false});
}
optionsState[product.productAttributes[index].name] = optionState;
}
if (selections.containsKey(product.productAttributes[index].name.toUpperCase())) {
Map<String, dynamic> attrExtraJson = Utils.stringToJson(
product.productAttributes[index].extra);
if (attrExtraJson != null) {
var selectLimitIfFieldEqualsTo = Rule.getRule(
attrExtraJson, Rule.RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO);
if (selectLimitIfFieldEqualsTo != null) {
if (selectLimitIfFieldEqualsTo is List) {
for (var b = 0; b < (selectLimitIfFieldEqualsTo as List).length; b++) {
Map<String, dynamic> selectLimitIfFieldEqualsTo1 = selectLimitIfFieldEqualsTo[b];
if (selectLimitIfFieldEqualsTo1.containsKey(
Rule.RULE_KEY_FORCE_LIMITED)) {
int limitQty = selectLimitIfFieldEqualsTo1[Rule
.RULE_KEY_FORCE_LIMITED];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
if (selectLimitIfFieldEqualsTo1.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0])) {
int limitQty = selectLimitIfFieldEqualsTo1[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo1[Rule.RULE_KEY_FIELD_KEY])[0]];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
}
}
}
} else {
if (selectLimitIfFieldEqualsTo.containsKey(
Rule.RULE_KEY_FORCE_LIMITED)) {
int limitQty = selectLimitIfFieldEqualsTo[Rule
.RULE_KEY_FORCE_LIMITED];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
} else if (Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY]).length > 0) {
if (selectLimitIfFieldEqualsTo.containsKey(Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0])) {
int limitQty = selectLimitIfFieldEqualsTo[Utils.getSelectedAttributeValue(selections, selectLimitIfFieldEqualsTo[Rule.RULE_KEY_FIELD_KEY])[0]];
thisLimitQty = limitQty;
if ((selections[product.productAttributes[index].name
.toUpperCase()] as List).length >= limitQty) {
disableOptionIfNotSelected();
}
}
}
}
}
}
for (var i = 0; i < productOptions.length; i++) {
Map<String, dynamic> extraJson = Utils.stringToJson(
productOptions[i].extra);
if (extraJson != null) {
Map<String, dynamic> exclusiveRule = Rule.getRule(
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
Map<String, dynamic> multiItemRule = Rule.getRule(extraJson, Rule.RULE_ACTUAL_QTY_IS);
if (exclusiveRule != null) {
if (thisLimitQty > 0 && !_checkOptionIsCheck(productOptions[i].name)) {
optionsState[product.productAttributes[index].name][i]['disabled'] = true;
} else {
if (_checkOptionIsCheck(productOptions[i].name)) {
setOptionsStateDisabled(
product.productAttributes[index].name, true);
optionsState[product.productAttributes[index]
.name][i]['disabled'] = false;
break;
}
}
}
if (multiItemRule != null) {
if (_checkOptionIsCheck(productOptions[i].name)) {
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
disableOptionIfNotSelected();
}
} else {
if (multiItemRule[Rule.RULE_ACTUAL_QTY_IS] > thisLimitQty - (selections[product.productAttributes[index].name.toUpperCase()] as List).length) {
optionsState[product.productAttributes[index]
.name][i]['disabled'] = true;
}
}
}
}
}
}
List<Map<String, dynamic>> optionState = optionsState[product.productAttributes[index].name];
for (var i = 0; i < optionState.length; i++) {
Widget optionWidget = _getOptionQty(
product.productAttributes[index].productOptions[i], optionState[i]['disabled'], optionState[i]['quantity'], i);
row.children.add(optionWidget);
}
return row;
}
void disableOptionIfNotSelected() {
setOptionsStateDisabled(product.productAttributes[index].name, true);
for (var i = 0; i < optionsState[product.productAttributes[index].name].length; i++) {
if (Utils.selectionsContains(selections, product.productAttributes[index].name, optionsState[product.productAttributes[index].name][i]['name']) != -1) {
optionsState[product.productAttributes[index].name][i]['disabled'] = false;
}
}
}
bool _checkOptionIsCheck(String name) {
if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) {
return true;
}
return false;
}
Widget _getOptionQty(ProductOption productOption, bool optDisabled, int quantity, int optIndex) {
bool disabled = optDisabled;
bool check = _checkOptionIsCheck(productOption.name);
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
// Utils.jsonPrettyPrint(extraJson);
double extraAdjustAmount = 0.0;
if (extraJson != null) {
var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE);
if (sizeBaseAdjustment != null) {
if (sizeBaseAdjustment is List) {
for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) {
Map<String, dynamic> sizeBaseAdjustment1 = sizeBaseAdjustment[i];
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) {
extraAdjustAmount += sizeBaseAdjustment1[attValues[0]];
}
}
} else {
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) {
extraAdjustAmount += sizeBaseAdjustment[attValues[0]];
}
}
}
var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO);
if (disabledRule != null) {
if (disabledRule is List) {
for (var i = 0; i < (disabledRule as List).length; i++) {
Map<String, dynamic> disabledRule1 = disabledRule[i];
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) {
disabled = true;
}
}
} else {
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) {
disabled = true;
}
}
}
}
return new Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
shape: BoxShape.rectangle,
border: new Border.all(
color: check ? selectedBorderColor : deselectBorderColor,
width: 1.0,
),
// borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0),
),
),
margin: new EdgeInsets.all(10.0).copyWith(right: 0.0),
child: new Column(
children: <Widget>[
new GestureDetector(
child: new Container(
width: 100.0,
height: 70.0,
padding: new EdgeInsets.all(4.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
productOption.name,
style: new TextStyle(
fontSize: 14.0,
color: check ? selectedTextColor : deselectTextColor,
),
maxLines: 2,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
new Text(
(productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '',
style: new TextStyle(
fontSize: 11.0,
color: check ? selectedTextColor : new Color(0xFFABABAB),
),
overflow: TextOverflow.ellipsis,
)
],
),
),
onTap: () => disabled ? null : _onOptionTappedCallback(
productOption.name, 1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
),
new Container(
width: 100.0,
height: 28.0,
decoration: BoxDecoration(
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
shape: BoxShape.rectangle,
border: new Border(
top: new BorderSide(
color: check ? selectedBorderColor : deselectBorderColor,
width: 1.0,
),
),
),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Expanded(
child: new GestureDetector(
child: new Container(
color: new Color(0x00000000),
width: 49.0,
height: 28.0,
child: new Center(
child: new Text(
'${quantity}',
style: new TextStyle(
color: check ? selectedTextColor : new Color(0xFFABABAB),
),
),
),
),
onTap: () => disabled ? null : _onOptionTappedCallback(
productOption.name, 1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
),
),
new Expanded(
child: new GestureDetector(
child: new Container(
color: disabledBackgroundColor,
width: 49.0,
height: 28.0,
child: new Center(
child: new Text('-')
),
),
onTap: () => (disabled || quantity == 0) ? null : _onOptionTappedCallback(
productOption.name, -1, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
),
),
],
),
)
],
),
);
}
void setOptionsStateDisabled(String attrName, bool disabled) {
if (optionsState.containsKey(attrName)) {
List<Map<String, dynamic>> optionState = optionsState[attrName];
for (var i = 0; i < optionState.length; i++) {
optionState[i]['disabled'] = disabled;
}
}
}
}

View File

@@ -0,0 +1,261 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import '../../../events/eventbus.dart';
import '../../../events/events.dart';
import '../../../generated/l10n.dart';
import '../../../models/product.dart';
import '../../../models/product_option.dart';
import '../../../utils/utils.dart';
import 'options_base.dart';
import 'rules.dart';
class RadioOptions extends OptionsBase {
RadioOptions({@required Product product, @required int index, @required Map<String, dynamic> selections})
: super(product: product, index: index, selections: selections);
@override
State<StatefulWidget> createState() {
return new RadioOptionsState();
}
}
class RadioOptionsState extends OptionsBaseState<RadioOptions> {
Map<String, dynamic> optionsState = new Map();
@override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: 2,
itemBuilder: (context, index) {
if (index == 0) {
return new Container(
padding: new EdgeInsets.all(10.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
S.of(context).radio_option_select_token(product.productAttributes[this.index].name),
style: new TextStyle(
fontSize: 12.5,
),
overflow: TextOverflow.ellipsis,
),
new Text(
product.productAttributes[this.index].required ? S.of(context).radio_option_is_required : S.of(context).radio_option_is_optional,
style: new TextStyle(
fontSize: 10.0,
color: new Color(0xFF999999)
),
overflow: TextOverflow.ellipsis,
)
],
),
);
}
return _getOptionWidget();
}
);
}
@override
void initState() {
super.initState();
setState(() {
product = widget.product;
selections = widget.selections;
index = widget.index;
});
eventBus.on<OnAttributePageChanged>().listen((event) {
setState(() {
index = event.index;
});
});
}
void _onOptionTappedCallback(name, quantity, adjustAmount, int optIndex, ProductOption productOption) {
Map<String, dynamic> opt = {
'name': name,
'quantity': quantity,
'adjust_amount': adjustAmount
};
var cloneSelections = json.decode(json.encode(selections));
if (product.productAttributes[index].required) {
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
} else {
if (cloneSelections.containsKey(product.productAttributes[index].name.toUpperCase())
&& Utils.equalsIgnoreCase(cloneSelections[product.productAttributes[index].name.toUpperCase()][0]['name'], name)) {
cloneSelections.remove(product.productAttributes[index].name.toUpperCase());
} else {
cloneSelections[product.productAttributes[index].name.toUpperCase()] = [opt];
}
}
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
if (extraJson != null) {
Map<String, dynamic> exclusiveRule = Rule.getRule(
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
if (exclusiveRule != null) {
if (_checkOptionIsCheck(productOption.name)) {
setOptionsStateDisabled(product.productAttributes[index].name, false);
}
}
}
setState(() {
selections = cloneSelections;
});
eventBus.fire(new OnAttributeSelectionsChanged(selections));
}
Widget _getOptionWidget() {
var row = Wrap(
children: <Widget>[],
);
List<ProductOption> productOptions = product.productAttributes[index].productOptions;
if (!optionsState.containsKey(product.productAttributes[index].name)) {
List<Map<String, dynamic>> optionState = [];
for (var i = 0; i < productOptions.length; i++) {
optionState.add({'name': product.productAttributes[index].productOptions[i].name, 'disabled': false, 'check': false});
}
optionsState[product.productAttributes[index].name] = optionState;
}
for (var i = 0; i < productOptions.length; i++) {
Map<String, dynamic> extraJson = Utils.stringToJson(productOptions[i].extra);
if (extraJson != null) {
Map<String, dynamic> exclusiveRule = Rule.getRule(
extraJson, Rule.RULE_EXCLUSIVE_SELECTION);
if (exclusiveRule != null) {
if (_checkOptionIsCheck(productOptions[i].name)) {
setOptionsStateDisabled(product.productAttributes[index].name, true);
optionsState[product.productAttributes[index].name][i]['disabled'] = false;
break;
}
}
}
}
List<Map<String, dynamic>> optionState = optionsState[product.productAttributes[index].name];
for (var i = 0; i < optionState.length; i++) {
Widget optionWidget = _getOptionRadio(
product.productAttributes[index].productOptions[i], optionState[i]['disabled'], i);
row.children.add(optionWidget);
}
return row;
}
bool _checkOptionIsCheck(String name) {
if (Utils.selectionsContains(selections, product.productAttributes[index].name, name) != -1) {
return true;
}
return false;
}
Widget _getOptionRadio(ProductOption productOption, bool optDisabled, int optIndex) {
bool disabled = optDisabled;
bool check = _checkOptionIsCheck(productOption.name);
Map<String, dynamic> extraJson = Utils.stringToJson(productOption.extra);
// Utils.jsonPrettyPrint(extraJson);
double extraAdjustAmount = 0.0;
if (extraJson != null) {
var sizeBaseAdjustment = Rule.getRule(extraJson, Rule.RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE);
if (sizeBaseAdjustment != null) {
if (sizeBaseAdjustment is List) {
for (var i = 0; i < (sizeBaseAdjustment as List).length; i++) {
Map<String, dynamic> sizeBaseAdjustment1 = sizeBaseAdjustment[i];
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment1[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && sizeBaseAdjustment1.containsKey(attValues[0])) {
extraAdjustAmount += sizeBaseAdjustment1[attValues[0]];
}
}
} else {
List<String> attValues = Utils.getSelectedAttributeValue(selections, sizeBaseAdjustment[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && sizeBaseAdjustment.containsKey(attValues[0])) {
extraAdjustAmount += sizeBaseAdjustment[attValues[0]];
}
}
}
var disabledRule = Rule.getRule(extraJson, Rule.RULE_DISABLED_IF_FIELD_EQUALS_TO);
if (disabledRule != null) {
if (disabledRule is List) {
for (var i = 0; i < (disabledRule as List).length; i++) {
Map<String, dynamic> disabledRule1 = disabledRule[i];
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule1[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && disabledRule1.containsKey(attValues[0])) {
disabled = true;
}
}
} else {
List<String> attValues = Utils.getSelectedAttributeValue(selections, disabledRule[Rule.RULE_KEY_FIELD_KEY]);
if (attValues.length > 0 && disabledRule.containsKey(attValues[0])) {
disabled = true;
}
}
}
}
return new GestureDetector(
child: new Container(
width: 100.0,
height: 70.0,
decoration: BoxDecoration(
color: disabled ? disabledBackgroundColor : (check ? selectedBackgroundColor : deselectBackgroundColor),
shape: BoxShape.rectangle,
border: new Border.all(
color: check ? selectedBorderColor : deselectBorderColor,
width: 1.0,
),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
padding: new EdgeInsets.all(4.0),
margin: new EdgeInsets.all(10.0).copyWith(right: 0.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
productOption.name,
style: new TextStyle(
fontSize: 14.0,
color: check ? selectedTextColor : deselectTextColor,
),
maxLines: 2,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
new Text(
(productOption.adjustAmount + extraAdjustAmount) > 0 ? '+${(productOption.adjustAmount + extraAdjustAmount).toStringAsFixed(2)}' : '',
style: new TextStyle(
fontSize: 11.0,
color: check ? selectedTextColor : new Color(0xFFABABAB),
),
overflow: TextOverflow.ellipsis,
)
],
),
),
onTap: () => disabled ? null : _onOptionTappedCallback(
productOption.name, 0, productOption.adjustAmount + extraAdjustAmount, optIndex, productOption),
);
}
void setOptionsStateDisabled(String attrName, bool disabled) {
if (optionsState.containsKey(attrName)) {
Map<String, dynamic> optionState = optionsState[attrName];
for (var i = 0; i < optionState.length; i++) {
optionState[i]['disabled'] = disabled;
}
}
}
}

View File

@@ -0,0 +1,27 @@
class Rule {
static final String rulesName = 'rules';
static final String RULE_ADJUSTMENT_BASED_ON_SELECTED_FIELD_VALUE = "adjustment-based-on-selected-field-value";
static final String RULE_SELECT_LIMIT_IF_FIELD_EQUALS_TO = "select-limit-if-field-equals-to";
static final String RULE_DISABLED_IF_FIELD_EQUALS_TO = "disabled-if-field-equals-to";
static final String RULE_EXCLUSIVE_SELECTION = "exclusive-selection";
static final String RULE_MAX_QTY_LIMIT = "max-qty-limit";
static final String RULE_ACTUAL_QTY_IS = "actual-quantity-is";
static final String RULE_KEY_FORCE_LIMITED = 'force_limited';
static final String RULE_KEY_FIELD_KEY = 'field-key';
static dynamic getRule(Map<String, dynamic> json, String ruleName) {
if (json != null && json.containsKey(rulesName)) {
if ((json[rulesName] as Map).containsKey(ruleName)) {
if (json[rulesName][ruleName] is bool) {
return {ruleName: true};
} else if (json[rulesName][ruleName] is int) {
return {ruleName: json[rulesName][ruleName]};
}
return json[rulesName][ruleName];
}
}
return null;
}
}

View File

@@ -17,8 +17,8 @@ class BottomNavState extends State<BottomNav> {
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 50.0,
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
height: 70.0,
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
decoration: BoxDecoration(
color: Color(0xff232323),
),

View File

@@ -0,0 +1,192 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import '../../generated/l10n.dart';
import '../../routes.dart';
class BreadCrumbs extends StatelessWidget {
final List<BreadCrumb> breadCrumbs;
final bool hasBack;
const BreadCrumbs(this.hasBack, {this.breadCrumbs, Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Widget> widgets = [];
if (this.hasBack) {
widgets.add(Container(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.arrow_back_ios,
size: 16.0,
color: Colors.black38,
),
Container(
padding: EdgeInsets.only(left: 0.0, right: 4.0),
child: Text(
S.of(context).back,
style: TextStyle(
color: Colors.black38,
fontSize: 14.0,
),
),
),
],
),
onTap: () {
Routes.router.pop(context);
},
),
),
));
}
if (breadCrumbs != null) {
for (int i = 0; i < breadCrumbs.length; i++) {
BreadCrumb breadCrumb = breadCrumbs[i];
if (breadCrumb.text == null && breadCrumb.item != null) {
if (breadCrumb.onTap == null) {
widgets.add(breadCrumb.item);
} else {
widgets.add(MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: breadCrumb.item,
onTap: breadCrumb.onTap,
),
));
}
} else {
if (breadCrumb.route != null) {
widgets.add(Container(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(top: 0.0),
child: Icon(
Icons.circle,
size: 6.0,
color: Colors.black38,
),
),
Container(
padding: EdgeInsets.only(left: 4.0, right: 4.0),
child: breadCrumb.icon != null ?
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
breadCrumb.icon,
size: 16.0,
color: Colors.blueAccent,
),
Text(
breadCrumb.text,
style: TextStyle(
color: Colors.blueAccent,
fontSize: 14.0,
),
)
],
) :
Text(
breadCrumb.text,
style: TextStyle(
color: Colors.blueAccent,
fontSize: 14.0,
),
),
),
],
),
onTap: () {
Routes.router.navigateTo(context, breadCrumb.route);
},
),
),
));
} else {
widgets.add(Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(top: 0.0),
child: Icon(
Icons.circle,
size: 6.0,
color: Colors.black38,
),
),
Container(
padding: EdgeInsets.only(left: 4.0, right: 4.0),
child: breadCrumb.icon != null ?
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
breadCrumb.icon,
size: 16.0,
color: Colors.lightBlueAccent,
),
Text(
breadCrumb.text,
style: TextStyle(
color: Colors.black54,
fontSize: 14.0,
fontWeight: FontWeight.bold,
),
)
],
) :
Text(
breadCrumb.text,
style: TextStyle(
color: Colors.black54,
fontSize: 14.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
));
}
}
}
}
return Container(
padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8.0, top: 8.0),
height: 38,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: widgets,
),
);
}
}
class BreadCrumb {
final String text;
final String route;
final IconData icon;
final Widget item;
final Function onTap;
BreadCrumb(this.text, this.route, {this.icon, this.item, this.onTap});
}

View File

@@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'style.dart';
class Carousel extends StatefulWidget {
Carousel({
double height = 200.0,
List<Widget> pages,
bool autoPlay,
Duration duration = const Duration(seconds: 2),
Duration animationDuration = const Duration(milliseconds: 1000),
})
: height = height,
pages = pages,
autoPlay = autoPlay,
duration = duration,
animationDuration = animationDuration;
final double height;
final List<Widget> pages;
final bool autoPlay;
final Duration duration;
final Duration animationDuration;
@override
createState() => new CarouselState();
}
class CarouselState extends State<Carousel> {
final _pageController = new PageController();
Timer _timer;
int _currentPage = 0;
bool reverse = false;
GlobalKey<IndicatorState> _indicatorStateKey = new GlobalKey();
@override
void initState() {
super.initState();
if (widget.autoPlay) {
_timer = new Timer.periodic(widget.duration, (timer) {
_pageController.animateToPage(_currentPage,
duration: widget.animationDuration, curve: Curves.linear);
if (!reverse) {
_currentPage += 1;
if (_currentPage == widget.pages.length) {
_currentPage -= 1;
reverse = true;
}
} else {
_currentPage -= 1;
if (_currentPage < 0) {
_currentPage += 1;
reverse = false;
}
}
});
}
}
@override
void dispose() {
_pageController?.dispose();
if (_timer != null) {
_timer.cancel();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
new Container(
height: widget.height,
color: Style.backgroundColor,
child: new PageView(
controller: _pageController,
children: widget.pages,
onPageChanged: (index) {
_currentPage = index;
_indicatorStateKey.currentState.changeIndex(index);
},
),
),
new Positioned(
left: 0.0,
right: 0.0,
bottom: 0.0,
child: new Align(
child: new Indicator(
key: _indicatorStateKey,
count: widget.pages.length,
),
alignment: Alignment.center,
),
),
],
);
}
}
class Indicator extends StatefulWidget {
Indicator({Key key, int count})
: count = count,
super(key: key);
final int count;
@override
createState() => new IndicatorState();
}
class IndicatorState extends State<Indicator> {
int _index = 0;
changeIndex(int index) {
setState(() {
_index = index;
});
}
@override
Widget build(BuildContext context) {
var indicators = <Widget>[];
for (var i = 0; i < widget.count; ++i) {
indicators.add(new Container(
width: 5.0,
height: 5.0,
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(5.0)),
color: _index == i ? Style.primaryColor : Style.borderColor,
),
));
}
return new SizedBox(
width: widget.count * 15.0,
height: 30.0,
child: new Row(
children: indicators,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import '../../generated/l10n.dart';
import '../../utils/double_back_to_close_app.dart';
class DoubleBackToCloseAppWrapper extends StatelessWidget {
final Widget child;
const DoubleBackToCloseAppWrapper({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return DoubleBackToCloseApp(
snackBar: SnackBar(
content: Container(
alignment: Alignment.center,
height: 24.0,
child: Text(
S.of(context).tap_back_again_to_exit,
),
),
),
child: child,
);
}
}

View File

@@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
import '../../utils/http_util.dart';
import '../../widgets/desktop/desktop_download_apps.dart';
import '../../widgets/mobile/mobile_download_apps.dart';
import 'package:responsive_builder/responsive_builder.dart';
class DownloadApps extends StatefulWidget {
const DownloadApps({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return DownloadAppsState();
}
}
class DownloadAppsState extends State<DownloadApps> {
Map<String, dynamic> data;
@override
Widget build(BuildContext context) {
return ScreenTypeLayout(
mobile: MobileDownloadApps(data),
tablet: DesktopDownloadApps(data),
desktop: DesktopDownloadApps(data),
);
}
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() {
HttpUtil.httpGet('v1/get-wisetronic-download-page')
.then((value) {
print('$value');
if (mounted) {
setState(() {
data = value;
});
}
});
}
}

View File

@@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../widgets/general/text_link.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../generated/l10n.dart';
@@ -31,7 +30,7 @@ class DownloadItemState extends State<DownloadItem> {
Widget build(BuildContext context) {
return Container(
width: widget.width ?? MediaQuery.of(context).size.width,
padding: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0, right: 10.0),
padding: EdgeInsets.only(top: 10.0, bottom: 20.0, left: 10.0, right: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -45,16 +44,8 @@ class DownloadItemState extends State<DownloadItem> {
child: Row(
children: [
Container(
child: (kIsWeb) ?
Image.network(
child: Util.showImage(
'${widget.desc['app_icon']}',
) :
SvgPicture.network(
'${widget.desc['app_icon']}',
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
),
width: iconWidth,
height: iconWidth,
@@ -210,7 +201,7 @@ class DownloadItemState extends State<DownloadItem> {
break;
}
}
print('download url $downloadUrl');
if (downloadUrl != null && downloadUrl == 'instore') {
return Container(
padding: EdgeInsets.all(8.0),

View File

@@ -1,21 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/widgets/desktop/desktop_navigationbar.dart';
import 'package:flutter_wisetronic/widgets/mobile/mobile_navigationbar.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../widgets/desktop/desktop_navigationbar.dart';
import '../../widgets/mobile/mobile_navigationbar.dart';
import 'breadcrumbs.dart';
class NavigationBar extends StatefulWidget implements PreferredSizeWidget {
final Key key;
final PreferredSizeWidget bottom;
final String title;
final bool back;
final bool toHome;
final bool showMe;
final List<BreadCrumb> breadCrumbs;
final double breadCrumbHeight;
final Widget shoppingCart;
NavigationBar({Key key, PreferredSizeWidget bottom, String title, bool back})
NavigationBar({Key key, PreferredSizeWidget bottom, String title,
bool back, bool toHome, bool showMe, this.breadCrumbs,
this.breadCrumbHeight, this.shoppingCart})
: key = key,
preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
bottom = bottom,
preferredSize = breadCrumbHeight != null ? Size.fromHeight(kToolbarHeight + breadCrumbHeight) :
Size.fromHeight(kToolbarHeight),
title = title ?? '',
back = back ?? false;
back = back ?? false,
toHome = toHome ?? false,
showMe = showMe ?? true;
@override
final Size preferredSize;
@@ -32,10 +42,17 @@ class NavigationBarState extends State<NavigationBar> {
@override
Widget build(BuildContext context) {
return ScreenTypeLayout(
mobile: MobileNavigationBar(title: widget.title, back: widget.back,),
tablet: DesktopNavigationBar(),
desktop: DesktopNavigationBar(),
mobile: MobileNavigationBar(title: widget.title, back: widget.back,
toHome: widget.toHome, showMe: widget.showMe,),
tablet: DesktopNavigationBar(hasBack: widget.back,
breadCrumbs: widget.breadCrumbs, shoppingCart: widget.shoppingCart,),
desktop: DesktopNavigationBar(hasBack: widget.back,
breadCrumbs: widget.breadCrumbs, shoppingCart: widget.shoppingCart,),
);
}
@override
void initState() {
super.initState();
}
}

View File

@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
// ignore: must_be_immutable
class ParabolicAnimationWidget extends AnimatedWidget {
final GlobalKey stackKey;
final GlobalKey startKey;
final GlobalKey endKey;
final double size;
final Color color;
final Offset startAdjustOffset;
final Offset endAdjustOffset;
ParabolicAnimationWidget({
@required Animation<double> animation,
@required this.stackKey,
@required this.startKey,
@required this.endKey,
this.size = 20.0,
this.color = Colors.red,
this.startAdjustOffset = Offset.zero,
this.endAdjustOffset = Offset.zero,
}) : super(listenable: animation);
Offset _startOffset;
Offset _endOffset;
@override
Widget build(BuildContext context) {
_calPoints();
final Animation<double> animation = listenable;
final double time = animation.value;
// 设time=1 已知两点坐标 和 初速度 可求出 加速度 a
// double x(double time) => _x + _v * time + 0.5 * _a * time * time;
final double initV = -400; //纵坐标初速度, 负值为向上抛
final double acceleration =
(_endOffset.dy - _startOffset.dy - initV) / 0.5; //求纵坐标加速度
final GravitySimulation spy = GravitySimulation(
acceleration, _startOffset.dy, _endOffset.dy, initV); //y轴加速度运动 模拟
final GravitySimulation spx = GravitySimulation(0, _startOffset.dx,
_endOffset.dx, _endOffset.dx - _startOffset.dx); //x轴匀速模拟 加速度为0
final Animation<double> opacity = Tween<double>(begin: 1, end: 0).animate(
CurvedAnimation(
parent: animation, curve: Interval(0.9, 1, curve: Curves.ease)));
return Positioned(
top: spy.x(time),
left: spx.x(time),
child: Opacity(
opacity: opacity.value,
child: Container(
height: size,
width: size,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.all(Radius.circular(size / 2.0))),
),
),
);
}
void _calPoints() {
if (_startOffset == null) {
RenderBox stackBox = stackKey.currentContext.findRenderObject();
Offset stackBoxOffset = stackBox.globalToLocal(Offset.zero);
EdgeInsets startMargin = _margin(startKey);
RenderBox startBox = startKey.currentContext.findRenderObject();
_startOffset = startBox.localToGlobal(Offset(
startMargin.left + startAdjustOffset.dx,
stackBoxOffset.dy + startMargin.top + startAdjustOffset.dy));
EdgeInsets endMargin = _margin(endKey);
RenderBox endBox = endKey.currentContext.findRenderObject();
_endOffset = endBox.localToGlobal(Offset(
endMargin.left + endAdjustOffset.dx,
stackBoxOffset.dy + endMargin.top + endAdjustOffset.dy));
}
}
EdgeInsets _margin(GlobalKey key) {
final Widget widget = key.currentContext.widget;
EdgeInsets margin = (widget is Container) ? widget.margin : EdgeInsets.zero;
return margin ?? EdgeInsets.zero;
}
}

View File

@@ -0,0 +1,176 @@
import 'package:countdown/countdown.dart';
import 'package:flutter/material.dart';
import 'package:pinput/pin_put/pin_put.dart';
import '../../generated/l10n.dart';
import '../../models/user.dart';
import '../../utils/http_util.dart';
import '../../utils/utils.dart';
typedef OnCodeVerified();
typedef OnCancel();
class PaymentVerificationCodeDialog extends StatefulWidget {
final Key key;
final User user;
final OnCodeVerified onCodeVerified;
final OnCancel onCancel;
const PaymentVerificationCodeDialog(this.user, this.onCodeVerified, this.onCancel, {this.key});
@override
State<StatefulWidget> createState() {
return PaymentVerificationCodeDialogState();
}
}
class PaymentVerificationCodeDialogState extends State<PaymentVerificationCodeDialog> {
CountDown cd = CountDown(Duration(seconds: 90));
var countDownListener;
bool enableGetCode = false;
String getCodeText = '';
String paymentCodeEncrypt = '';
final TextEditingController _pinPutController = TextEditingController();
final FocusNode _pinPutFocusNode = FocusNode();
BoxDecoration get _pinPutDecoration {
return BoxDecoration(
border: Border.all(color: Colors.deepPurpleAccent),
borderRadius: BorderRadius.circular(15),
);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(S.of(context).payment_verification),
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0),
child: Text(
S.of(context).payment_verification_sent(Utils.safePhoneNumber(widget.user.mobile)),
),
),
Container(
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0),
child: FlatButton(
child: Text(getCodeText),
onPressed: enableGetCode ? () {
getPaymentCode();
} : null,
),
),
Center(
child: Container(
padding: EdgeInsets.only(top: 0.0, bottom: 10.0, left: 0.0, right: 0.0),
child: PinPut(
fieldsCount: 4,
focusNode: _pinPutFocusNode,
controller: _pinPutController,
onSubmit: (String pin) {
String codeString = generateSignature(pin, key: widget.user.id.toString());
FocusScope.of(context).unfocus();
if (paymentCodeEncrypt == codeString) {
Navigator.of(context).pop();
if (widget.onCodeVerified != null) {
widget.onCodeVerified();
}
} else {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).error),
content: Text(S.of(context).wrong_payment_verification_code),
actions: [
FlatButton(
child: Text(S.of(context).ok),
onPressed: () {
_pinPutController.clear();
_pinPutFocusNode.requestFocus();
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
submittedFieldDecoration: _pinPutDecoration.copyWith(
borderRadius: BorderRadius.circular(20)),
selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration.copyWith(
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Colors.deepPurpleAccent.withOpacity(.5),
),
),
),
),
),
],
),
actions: [
FlatButton(
child: Text(S.of(context).close),
onPressed: () {
Navigator.of(context).pop();
if (widget.onCancel != null) {
widget.onCancel();
}
},
)
],
);
}
@override
void initState() {
super.initState();
getPaymentCode();
}
void getPaymentCode() {
HttpUtil.httpGet('v1/get-payment-verification-code',
queryParameters: {
'key': widget.user.id,
'method': 'mobile', // or 'mobile'
'is_user': false,
},
).then((data) {
paymentCodeEncrypt = data['payment_code_encrypt'];
print('data: $data');
startCountDown();
}).catchError((error) {
Utils.showMessageDialog(context, error);
});
}
void startCountDown() {
countDownListener = CountDown(Duration(seconds: 90)).stream.listen(null);
countDownListener.onData((Duration d) {
if (mounted) {
setState(() {
enableGetCode = false;
getCodeText = S.of(context).get_code_token(d.inSeconds);
});
}
});
countDownListener.onDone(() {
if (mounted) {
setState(() {
enableGetCode = true;
getCodeText = S.of(context).get_code_again;
});
}
});
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class PopupAnimationWidget extends AnimatedWidget {
final GlobalKey stackKey;
final GlobalKey startKey;
final Color color;
final Widget child;
final Offset popupOffset;
final Animation<double> animation;
PopupAnimationWidget({
@required this.animation,
@required this.stackKey,
@required this.startKey,
@required this.child,
this.color = Colors.yellow,
this.popupOffset = Offset.zero,
}) : super(listenable: animation);
Offset _startOffset;
Offset _offset = Offset.zero;
@override
Widget build(BuildContext context) {
_calAnimation();
final Animation<double> opacityAnimation = Tween<double>(begin: 0, end: 1)
.animate(CurvedAnimation(
parent: animation, curve: Interval(0, 0.4, curve: Curves.ease)));
_offset =
Offset(_startOffset.dx, _startOffset.dy - opacityAnimation.value * 80);
return Positioned(
left: _offset.dx,
top: _offset.dy,
child: Opacity(
opacity: opacityAnimation.value,
child: ScaleTransition(
alignment: Alignment.bottomCenter,
scale: opacityAnimation,
child: Container(
child: child,
),
),
),
);
}
void _calAnimation() {
if (_startOffset == null) {
final RenderBox stackBox = stackKey.currentContext.findRenderObject();
final Offset stackBoxOffset = stackBox.globalToLocal(Offset.zero);
final EdgeInsets startMargin = _margin(startKey);
final RenderBox startBox = startKey.currentContext.findRenderObject();
_startOffset = startBox.localToGlobal(Offset(
startMargin.left + popupOffset.dx,
stackBoxOffset.dy + startMargin.top + popupOffset.dy));
}
}
EdgeInsets _margin(GlobalKey key) {
final Widget widget = key.currentContext.widget;
final EdgeInsets margin =
(widget is Container) ? widget.margin : EdgeInsets.zero;
return margin ?? EdgeInsets.zero;
}
}

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/models/business.dart';
import 'package:flutter_wisetronic/models/product.dart';
import 'package:flutter_wisetronic/widgets/desktop/desktop_product_item.dart';
import 'package:flutter_wisetronic/widgets/mobile/mobile_product_item.dart';
import 'package:responsive_builder/responsive_builder.dart';
class ProductItem extends StatelessWidget {
final Product product;
final Business business;
const ProductItem({Key key, this.product, this.business}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileProductItem(product: product, business: business,),
tablet: DesktopProductItem(product: product, business: business,),
desktop: DesktopProductItem(product: product, business: business,),
),
);
}
}

View File

@@ -0,0 +1,198 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
enum TrimMode {
Length,
Line,
}
class ReadMoreText extends StatefulWidget {
const ReadMoreText(
this.data, {
Key key,
this.trimExpandedText = ' read less',
this.trimCollapsedText = ' ...read more',
this.colorClickableText,
this.trimLength = 240,
this.trimLines = 2,
this.trimMode = TrimMode.Length,
this.style,
this.textAlign,
this.textDirection,
this.locale,
this.textScaleFactor,
this.semanticsLabel,
}) : assert(data != null),
super(key: key);
final String data;
final String trimExpandedText;
final String trimCollapsedText;
final Color colorClickableText;
final int trimLength;
final int trimLines;
final TrimMode trimMode;
final TextStyle style;
final TextAlign textAlign;
final TextDirection textDirection;
final Locale locale;
final double textScaleFactor;
final String semanticsLabel;
@override
ReadMoreTextState createState() => ReadMoreTextState();
}
const String _kEllipsis = '\u2026';
const String _kLineSeparator = '\u2028';
class ReadMoreTextState extends State<ReadMoreText> {
bool _readMore = true;
void _onTapLink() {
setState(() => _readMore = !_readMore);
}
@override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = widget.style;
if (widget.style == null || widget.style.inherit) {
effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
}
final textAlign =
widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start;
final textDirection = widget.textDirection ?? Directionality.of(context);
final textScaleFactor =
widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context);
final overflow = defaultTextStyle.overflow;
final locale =
widget.locale ?? Localizations.localeOf(context);
final colorClickableText =
widget.colorClickableText ?? Theme.of(context).accentColor;
TextSpan link = TextSpan(
text: _readMore ? widget.trimCollapsedText : widget.trimExpandedText,
style: effectiveTextStyle.copyWith(
color: colorClickableText,
),
recognizer: TapGestureRecognizer()..onTap = _onTapLink,
);
Widget result = LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
assert(constraints.hasBoundedWidth);
final double maxWidth = constraints.maxWidth;
// Create a TextSpan with data
final text = TextSpan(
style: effectiveTextStyle,
text: widget.data,
);
// Layout and measure link
TextPainter textPainter = TextPainter(
text: link,
textAlign: textAlign,
textDirection: textDirection,
textScaleFactor: textScaleFactor,
maxLines: widget.trimLines,
ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
locale: locale,
);
textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth);
final linkSize = textPainter.size;
// Layout and measure text
textPainter.text = text;
textPainter.layout(minWidth: constraints.minWidth, maxWidth: maxWidth);
final textSize = textPainter.size;
print('linkSize $linkSize textSize $textSize');
// Get the endIndex of data
bool linkLongerThanLine = false;
int endIndex;
if (linkSize.width < maxWidth) {
final pos = textPainter.getPositionForOffset(Offset(
textSize.width - linkSize.width,
textSize.height,
));
endIndex = textPainter.getOffsetBefore(pos.offset);
}
else {
var pos = textPainter.getPositionForOffset(
textSize.bottomLeft(Offset.zero),
);
endIndex = pos.offset;
linkLongerThanLine = true;
}
var textSpan;
switch (widget.trimMode) {
case TrimMode.Length:
if (widget.trimLength < widget.data.length) {
textSpan = TextSpan(
style: effectiveTextStyle,
text: _readMore
? widget.data.substring(0, widget.trimLength)
: widget.data,
children: <TextSpan>[link],
);
} else {
textSpan = TextSpan(
style: effectiveTextStyle,
text: widget.data,
);
}
break;
case TrimMode.Line:
if (textPainter.didExceedMaxLines) {
textSpan = TextSpan(
style: effectiveTextStyle,
text: _readMore
? widget.data.substring(0, endIndex) +
(linkLongerThanLine ? _kLineSeparator : '')
: widget.data,
children: <TextSpan>[link],
);
} else {
textSpan = TextSpan(
style: effectiveTextStyle,
text: widget.data,
);
}
break;
default:
throw Exception(
'TrimMode type: ${widget.trimMode} is not supported');
}
return RichText(
textAlign: textAlign,
textDirection: textDirection,
softWrap: true,
//softWrap,
overflow: TextOverflow.clip,
//overflow,
textScaleFactor: textScaleFactor,
text: textSpan,
);
},
);
if (widget.semanticsLabel != null) {
result = Semantics(
textDirection: widget.textDirection,
label: widget.semanticsLabel,
child: ExcludeSemantics(
child: result,
),
);
}
return result;
}
}

View File

@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
class ShowPrice extends StatelessWidget {
final double price;
final double regularPrice;
final double largeFontSize;
final double smallFontSize;
final String currencySign;
final Color color;
final FontWeight fontWeight;
ShowPrice(this.price,
{
this.regularPrice,
this.largeFontSize = 20,
this.smallFontSize = 12,
this.currencySign,
this.color = Colors.red,
this.fontWeight = FontWeight.normal,
});
@override
Widget build(BuildContext context) {
String priceString = price.toStringAsFixed(2);
var arr = priceString.split('.');
String num1 = arr[0];
String num2 = arr[1];
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
currencySign != null ?
Container(
padding: EdgeInsets.only(top: largeFontSize / 10.0),
child: Text(
currencySign,
style: TextStyle(
fontSize: smallFontSize,
color: color,
fontWeight: fontWeight,
),
),
) : SizedBox.shrink(),
Container(
child: Text(
num1,
style: TextStyle(
color: color,
fontSize: largeFontSize,
fontWeight: fontWeight,
),
),
),
Container(
padding: EdgeInsets.only(top: largeFontSize / 10.0),
child: Text(
'.$num2',
style: TextStyle(
color: color,
fontSize: smallFontSize,
fontWeight: fontWeight,
),
),
),
regularPrice == null || price.round() >= regularPrice.round() ?
SizedBox.shrink() :
Container(
padding: EdgeInsets.only(top: largeFontSize / 2),
child: Text(
regularPrice.toStringAsFixed(0),
style: TextStyle(
color: Colors.black38,
fontSize: smallFontSize,
decoration: TextDecoration.lineThrough,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,711 @@
/*
Name: Akshath Jain
Date: 3/18/2019 - 4/2/2020
Purpose: Defines the sliding_up_panel widget
Copyright: © 2020, Akshath Jain. All rights reserved.
Licensing: More information can be found here: https://github.com/akshathjain/sliding_up_panel/blob/master/LICENSE
*/
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter/physics.dart';
enum SlideDirection{
UP,
DOWN,
}
enum PanelState{
OPEN,
CLOSED
}
class SlidingUpPanel extends StatefulWidget {
/// The Widget that slides into view. When the
/// panel is collapsed and if [collapsed] is null,
/// then top portion of this Widget will be displayed;
/// otherwise, [collapsed] will be displayed overtop
/// of this Widget. If [panel] and [panelBuilder] are both non-null,
/// [panel] will be used.
final Widget panel;
/// WARNING: This feature is still in beta and is subject to change without
/// notice. Stability is not gauranteed. Provides a [ScrollController] and
/// [ScrollPhysics] to attach to a scrollable object in the panel that links
/// the panel position with the scroll position. Useful for implementing an
/// infinite scroll behavior. If [panel] and [panelBuilder] are both non-null,
/// [panel] will be used.
final Widget Function(ScrollController sc) panelBuilder;
/// The Widget displayed overtop the [panel] when collapsed.
/// This fades out as the panel is opened.
final Widget collapsed;
/// The Widget that lies underneath the sliding panel.
/// This Widget automatically sizes itself
/// to fill the screen.
final Widget body;
/// Optional persistent widget that floats above the [panel] and attaches
/// to the top of the [panel]. Content at the top of the panel will be covered
/// by this widget. Add padding to the bottom of the `panel` to
/// avoid coverage.
final Widget header;
/// Optional persistent widget that floats above the [panel] and
/// attaches to the bottom of the [panel]. Content at the bottom of the panel
/// will be covered by this widget. Add padding to the bottom of the `panel`
/// to avoid coverage.
final Widget footer;
/// The height of the sliding panel when fully collapsed.
final double minHeight;
/// The height of the sliding panel when fully open.
final double maxHeight;
/// A point between [minHeight] and [maxHeight] that the panel snaps to
/// while animating. A fast swipe on the panel will disregard this point
/// and go directly to the open/close position. This value is represented as a
/// percentage of the total animation distance ([maxHeight] - [minHeight]),
/// so it must be between 0.0 and 1.0, exclusive.
final double snapPoint;
/// A border to draw around the sliding panel sheet.
final Border border;
/// If non-null, the corners of the sliding panel sheet are rounded by this [BorderRadiusGeometry].
final BorderRadiusGeometry borderRadius;
/// A list of shadows cast behind the sliding panel sheet.
final List<BoxShadow> boxShadow;
/// The color to fill the background of the sliding panel sheet.
final Color color;
/// The amount to inset the children of the sliding panel sheet.
final EdgeInsetsGeometry padding;
/// Empty space surrounding the sliding panel sheet.
final EdgeInsetsGeometry margin;
/// Set to false to not to render the sheet the [panel] sits upon.
/// This means that only the [body], [collapsed], and the [panel]
/// Widgets will be rendered.
/// Set this to false if you want to achieve a floating effect or
/// want more customization over how the sliding panel
/// looks like.
final bool renderPanelSheet;
/// Set to false to disable the panel from snapping open or closed.
final bool panelSnapping;
/// If non-null, this can be used to control the state of the panel.
final PanelController controller;
/// If non-null, shows a darkening shadow over the [body] as the panel slides open.
final bool backdropEnabled;
/// Shows a darkening shadow of this [Color] over the [body] as the panel slides open.
final Color backdropColor;
/// The opacity of the backdrop when the panel is fully open.
/// This value can range from 0.0 to 1.0 where 0.0 is completely transparent
/// and 1.0 is completely opaque.
final double backdropOpacity;
/// Flag that indicates whether or not tapping the
/// backdrop closes the panel. Defaults to true.
final bool backdropTapClosesPanel;
/// If non-null, this callback
/// is called as the panel slides around with the
/// current position of the panel. The position is a double
/// between 0.0 and 1.0 where 0.0 is fully collapsed and 1.0 is fully open.
final void Function(double position) onPanelSlide;
/// If non-null, this callback is called when the
/// panel is fully opened
final VoidCallback onPanelOpened;
/// If non-null, this callback is called when the panel
/// is fully collapsed.
final VoidCallback onPanelClosed;
/// If non-null and true, the SlidingUpPanel exhibits a
/// parallax effect as the panel slides up. Essentially,
/// the body slides up as the panel slides up.
final bool parallaxEnabled;
/// Allows for specifying the extent of the parallax effect in terms
/// of the percentage the panel has slid up/down. Recommended values are
/// within 0.0 and 1.0 where 0.0 is no parallax and 1.0 mimics a
/// one-to-one scrolling effect. Defaults to a 10% parallax.
final double parallaxOffset;
/// Allows toggling of the draggability of the SlidingUpPanel.
/// Set this to false to prevent the user from being able to drag
/// the panel up and down. Defaults to true.
final bool isDraggable;
/// Either SlideDirection.UP or SlideDirection.DOWN. Indicates which way
/// the panel should slide. Defaults to UP. If set to DOWN, the panel attaches
/// itself to the top of the screen and is fully opened when the user swipes
/// down on the panel.
final SlideDirection slideDirection;
/// The default state of the panel; either PanelState.OPEN or PanelState.CLOSED.
/// This value defaults to PanelState.CLOSED which indicates that the panel is
/// in the closed position and must be opened. PanelState.OPEN indicates that
/// by default the Panel is open and must be swiped closed by the user.
final PanelState defaultPanelState;
SlidingUpPanel({
Key key,
this.panel,
this.panelBuilder,
this.body,
this.collapsed,
this.minHeight = 100.0,
this.maxHeight = 500.0,
this.snapPoint,
this.border,
this.borderRadius,
this.boxShadow = const <BoxShadow>[
BoxShadow(
blurRadius: 8.0,
color: Color.fromRGBO(0, 0, 0, 0.25),
)
],
this.color = Colors.white,
this.padding,
this.margin,
this.renderPanelSheet = true,
this.panelSnapping = true,
this.controller,
this.backdropEnabled = false,
this.backdropColor = Colors.black,
this.backdropOpacity = 0.5,
this.backdropTapClosesPanel = true,
this.onPanelSlide,
this.onPanelOpened,
this.onPanelClosed,
this.parallaxEnabled = false,
this.parallaxOffset = 0.1,
this.isDraggable = true,
this.slideDirection = SlideDirection.UP,
this.defaultPanelState = PanelState.CLOSED,
this.header,
this.footer
}) : assert(panel != null || panelBuilder != null),
assert(0 <= backdropOpacity && backdropOpacity <= 1.0),
assert (snapPoint == null || 0 < snapPoint && snapPoint < 1.0),
super(key: key);
@override
_SlidingUpPanelState createState() => _SlidingUpPanelState();
}
class _SlidingUpPanelState extends State<SlidingUpPanel> with SingleTickerProviderStateMixin{
AnimationController _ac;
ScrollController _sc;
bool _scrollingEnabled = false;
VelocityTracker _vt = new VelocityTracker();
bool _isPanelVisible = true;
@override
void initState(){
super.initState();
_ac = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
value: widget.defaultPanelState == PanelState.CLOSED ? 0.0 : 1.0 //set the default panel state (i.e. set initial value of _ac)
)..addListener((){
if(widget.onPanelSlide != null) widget.onPanelSlide(_ac.value);
if(widget.onPanelOpened != null && _ac.value == 1.0) widget.onPanelOpened();
if(widget.onPanelClosed != null && _ac.value == 0.0) widget.onPanelClosed();
});
// prevent the panel content from being scrolled only if the widget is
// draggable and panel scrolling is enabled
_sc = new ScrollController();
_sc.addListener((){
if(widget.isDraggable && !_scrollingEnabled)
_sc.jumpTo(0);
});
widget.controller?._addState(this);
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: widget.slideDirection == SlideDirection.UP ? Alignment.bottomCenter : Alignment.topCenter,
children: <Widget>[
//make the back widget take up the entire back side
widget.body != null ? AnimatedBuilder(
animation: _ac,
builder: (context, child){
return Positioned(
top: widget.parallaxEnabled ? _getParallax() : 0.0,
child: child,
);
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: widget.body,
),
) : Container(),
//the backdrop to overlay on the body
!widget.backdropEnabled ? Container() : GestureDetector(
onVerticalDragEnd: widget.backdropTapClosesPanel ? (DragEndDetails dets){
// only trigger a close if the drag is towards panel close position
if((widget.slideDirection == SlideDirection.UP ? 1 : -1) * dets.velocity.pixelsPerSecond.dy > 0)
_close();
} : null,
onTap: widget.backdropTapClosesPanel ? () => _close() : null,
child: AnimatedBuilder(
animation: _ac,
builder: (context, _) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
//set color to null so that touch events pass through
//to the body when the panel is closed, otherwise,
//if a color exists, then touch events won't go through
color: _ac.value == 0.0 ? null : widget.backdropColor.withOpacity(widget.backdropOpacity * _ac.value),
);
}
),
),
//the actual sliding part
!_isPanelVisible ? Container() : _gestureHandler(
child: AnimatedBuilder(
animation: _ac,
builder: (context, child) {
return Container(
height: _ac.value * (widget.maxHeight - widget.minHeight) + widget.minHeight,
margin: widget.margin,
padding: widget.padding,
decoration: widget.renderPanelSheet ? BoxDecoration(
border: widget.border,
borderRadius: widget.borderRadius,
boxShadow: widget.boxShadow,
color: widget.color,
) : null,
child: child,
);
},
child: Stack(
overflow: Overflow.visible,
children: <Widget>[
//open panel
Positioned(
top: widget.slideDirection == SlideDirection.UP ? 0.0 : null,
bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null,
width: MediaQuery.of(context).size.width -
(widget.margin != null ? widget.margin.horizontal : 0) -
(widget.padding != null ? widget.padding.horizontal : 0),
child: Container(
height: widget.maxHeight,
child: widget.panel != null
? widget.panel
: widget.panelBuilder(_sc),
),
),
// header
widget.header != null ? Positioned(
top: widget.slideDirection == SlideDirection.UP ? 0.0 : null,
bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null,
child: widget.header,
) : Container(),
// footer
widget.footer != null ? Positioned(
top: widget.slideDirection == SlideDirection.UP ? null : 0.0,
bottom: widget.slideDirection == SlideDirection.DOWN ? null : 0.0,
child: widget.footer
) : Container(),
// collapsed panel
Positioned(
top: widget.slideDirection == SlideDirection.UP ? 0.0 : null,
bottom: widget.slideDirection == SlideDirection.DOWN ? 0.0 : null,
width: MediaQuery.of(context).size.width -
(widget.margin != null ? widget.margin.horizontal : 0) -
(widget.padding != null ? widget.padding.horizontal : 0),
child: Container(
height: widget.minHeight,
child: widget.collapsed == null ? Container() : FadeTransition(
opacity: Tween(begin: 1.0, end: 0.0).animate(_ac),
// if the panel is open ignore pointers (touch events) on the collapsed
// child so that way touch events go through to whatever is underneath
child: IgnorePointer(
ignoring: _isPanelOpen,
child: widget.collapsed
),
),
),
),
],
),
),
),
],
);
}
@override
void dispose(){
_ac.dispose();
super.dispose();
}
double _getParallax(){
if(widget.slideDirection == SlideDirection.UP)
return -_ac.value * (widget.maxHeight - widget.minHeight) * widget.parallaxOffset;
else
return _ac.value * (widget.maxHeight - widget.minHeight) * widget.parallaxOffset;
}
// returns a gesture detector if panel is used
// and a listener if panelBuilder is used.
// this is because the listener is designed only for use with linking the scrolling of
// panels and using it for panels that don't want to linked scrolling yields odd results
Widget _gestureHandler({Widget child}){
if (!widget.isDraggable) return child;
if (widget.panel != null){
return GestureDetector(
onVerticalDragUpdate: (DragUpdateDetails dets) => _onGestureSlide(dets.delta.dy),
onVerticalDragEnd: (DragEndDetails dets) => _onGestureEnd(dets.velocity),
child: child,
);
}
return Listener(
onPointerDown: (PointerDownEvent p) => _vt.addPosition(p.timeStamp, p.position),
onPointerMove: (PointerMoveEvent p){
_vt.addPosition(p.timeStamp, p.position); // add current position for velocity tracking
_onGestureSlide(p.delta.dy);
},
onPointerUp: (PointerUpEvent p) => _onGestureEnd(_vt.getVelocity()),
child: child,
);
}
// handles the sliding gesture
void _onGestureSlide(double dy){
// only slide the panel if scrolling is not enabled
if(!_scrollingEnabled){
if(widget.slideDirection == SlideDirection.UP)
_ac.value -= dy / (widget.maxHeight - widget.minHeight);
else
_ac.value += dy / (widget.maxHeight - widget.minHeight);
}
// if the panel is open and the user hasn't scrolled, we need to determine
// whether to enable scrolling if the user swipes up, or disable closing and
// begin to close the panel if the user swipes down
if(_isPanelOpen && _sc.hasClients && _sc.offset <= 0){
setState(() {
if(dy < 0){
_scrollingEnabled = true;
}else{
_scrollingEnabled = false;
}
});
}
}
// handles when user stops sliding
void _onGestureEnd(Velocity v){
double minFlingVelocity = 365.0;
double kSnap = 8;
//let the current animation finish before starting a new one
if(_ac.isAnimating) return;
// if scrolling is allowed and the panel is open, we don't want to close
// the panel if they swipe up on the scrollable
if(_isPanelOpen && _scrollingEnabled) return;
//check if the velocity is sufficient to constitute fling to end
double visualVelocity = -v.pixelsPerSecond.dy / (widget.maxHeight - widget.minHeight);
// reverse visual velocity to account for slide direction
if(widget.slideDirection == SlideDirection.DOWN)
visualVelocity = -visualVelocity;
// get minimum distances to figure out where the panel is at
double d2Close = _ac.value;
double d2Open = 1 - _ac.value;
double d2Snap = ((widget.snapPoint ?? 3) -_ac.value).abs(); // large value if null results in not every being the min
double minDistance = min(d2Close, min(d2Snap, d2Open));
// check if velocity is sufficient for a fling
if(v.pixelsPerSecond.dy.abs() >= minFlingVelocity){
// snapPoint exists
if(widget.panelSnapping && widget.snapPoint != null){
if(v.pixelsPerSecond.dy.abs() >= kSnap*minFlingVelocity || minDistance == d2Snap)
_ac.fling(velocity: visualVelocity);
else
_flingPanelToPosition(widget.snapPoint, visualVelocity);
// no snap point exists
}else if(widget.panelSnapping){
_ac.fling(velocity: visualVelocity);
// panel snapping disabled
}else{
_ac.animateTo(
_ac.value + visualVelocity * 0.16,
duration: Duration(milliseconds: 410),
curve: Curves.decelerate,
);
}
return;
}
// check if the controller is already halfway there
if (widget.panelSnapping) {
if(minDistance == d2Close){
_close();
}else if(minDistance == d2Snap){
_flingPanelToPosition(widget.snapPoint, visualVelocity);
}else{
_open();
}
}
}
void _flingPanelToPosition(double targetPos, double velocity){
final Simulation simulation = SpringSimulation(
SpringDescription.withDampingRatio(
mass: 1.0,
stiffness: 500.0,
ratio: 1.0,
),
_ac.value,
targetPos,
velocity
);
_ac.animateWith(simulation);
}
//---------------------------------
//PanelController related functions
//---------------------------------
//close the panel
Future<void> _close(){
return _ac.fling(velocity: -1.0);
}
//open the panel
Future<void> _open(){
return _ac.fling(velocity: 1.0);
}
//hide the panel (completely offscreen)
Future<void> _hide(){
return _ac.fling(velocity: -1.0).then((x){
setState(() {
_isPanelVisible = false;
});
});
}
//show the panel (in collapsed mode)
Future<void> _show(){
return _ac.fling(velocity: -1.0).then((x){
setState(() {
_isPanelVisible = true;
});
});
}
//animate the panel position to value - must
//be between 0.0 and 1.0
Future<void> _animatePanelToPosition(double value, {Duration duration, Curve curve = Curves.linear}){
assert(0.0 <= value && value <= 1.0);
return _ac.animateTo(value, duration: duration, curve: curve);
}
//animate the panel position to the snap point
//REQUIRES that widget.snapPoint != null
Future<void> _animatePanelToSnapPoint({Duration duration, Curve curve = Curves.linear}){
assert(widget.snapPoint != null);
return _ac.animateTo(widget.snapPoint, duration: duration, curve: curve);
}
//set the panel position to value - must
//be between 0.0 and 1.0
set _panelPosition(double value){
assert(0.0 <= value && value <= 1.0);
_ac.value = value;
}
//get the current panel position
//returns the % offset from collapsed state
//as a decimal between 0.0 and 1.0
double get _panelPosition => _ac.value;
//returns whether or not
//the panel is still animating
bool get _isPanelAnimating => _ac.isAnimating;
//returns whether or not the
//panel is open
bool get _isPanelOpen => _ac.value == 1.0;
//returns whether or not the
//panel is closed
bool get _isPanelClosed => _ac.value == 0.0;
//returns whether or not the
//panel is shown/hidden
bool get _isPanelShown => _isPanelVisible;
}
class PanelController{
_SlidingUpPanelState _panelState;
void _addState(_SlidingUpPanelState panelState){
this._panelState = panelState;
}
/// Determine if the panelController is attached to an instance
/// of the SlidingUpPanel (this property must return true before any other
/// functions can be used)
bool get isAttached => _panelState != null;
/// Closes the sliding panel to its collapsed state (i.e. to the minHeight)
Future<void> close(){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._close();
}
/// Opens the sliding panel fully
/// (i.e. to the maxHeight)
Future<void> open(){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._open();
}
/// Hides the sliding panel (i.e. is invisible)
Future<void> hide(){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._hide();
}
/// Shows the sliding panel in its collapsed state
/// (i.e. "un-hide" the sliding panel)
Future<void> show(){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._show();
}
/// Animates the panel position to the value.
/// The value must between 0.0 and 1.0
/// where 0.0 is fully collapsed and 1.0 is completely open.
/// (optional) duration specifies the time for the animation to complete
/// (optional) curve specifies the easing behavior of the animation.
Future<void> animatePanelToPosition(double value, {Duration duration, Curve curve = Curves.linear}){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
assert(0.0 <= value && value <= 1.0);
return _panelState._animatePanelToPosition(value, duration: duration, curve: curve);
}
/// Animates the panel position to the snap point
/// Requires that the SlidingUpPanel snapPoint property is not null
/// (optional) duration specifies the time for the animation to complete
/// (optional) curve specifies the easing behavior of the animation.
Future<void> animatePanelToSnapPoint({Duration duration, Curve curve = Curves.linear}){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
assert(_panelState.widget.snapPoint != null, "SlidingUpPanel snapPoint property must not be null");
return _panelState._animatePanelToSnapPoint(duration: duration, curve: curve);
}
/// Sets the panel position (without animation).
/// The value must between 0.0 and 1.0
/// where 0.0 is fully collapsed and 1.0 is completely open.
set panelPosition(double value){
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
assert(0.0 <= value && value <= 1.0);
_panelState._panelPosition = value;
}
/// Gets the current panel position.
/// Returns the % offset from collapsed state
/// to the open state
/// as a decimal between 0.0 and 1.0
/// where 0.0 is fully collapsed and
/// 1.0 is full open.
double get panelPosition{
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._panelPosition;
}
/// Returns whether or not the panel is
/// currently animating.
bool get isPanelAnimating{
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._isPanelAnimating;
}
/// Returns whether or not the
/// panel is open.
bool get isPanelOpen{
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._isPanelOpen;
}
/// Returns whether or not the
/// panel is closed.
bool get isPanelClosed{
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._isPanelClosed;
}
/// Returns whether or not the
/// panel is shown/hidden.
bool get isPanelShown{
assert(isAttached, "PanelController must be attached to a SlidingUpPanel");
return _panelState._isPanelShown;
}
}

View File

@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:stripe_payment/stripe_payment.dart';
import '../../constants.dart';
import '../../events/eventbus.dart';
import '../../events/events.dart';
import '../../models/order.dart';
import '../../models/payment_platform.dart';
import '../../models/stripe_payment_method.dart';
import '../../routes.dart';
import '../../store/actions.dart';
import '../../store/store.dart';
import '../../utils/utils.dart';
class StripePay extends StatefulWidget {
final Key key;
final Order order;
final PaymentPlatform paymentPlatform;
final StripePaymentMethod stripePaymentMethod;
const StripePay(this.order, this.paymentPlatform, {this.key, this.stripePaymentMethod});
@override
State<StatefulWidget> createState() {
return StripePayState();
}
}
class StripePayState extends State<StripePay> {
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
bool isSubmitting;
@override
Widget build(BuildContext context) {
store.dispatch(UpdateContext(context));
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (widget.stripePaymentMethod != null) {
_paymentWithPaymentMethod(context);
} else {
_paymentRequestWithCardForm(context);
}
});
return Scaffold(
key: _scaffoldKey,
body: Center(
child: Icon(
Icons.credit_card,
size: 40.0,
color: Colors.black26,
),
),
);
}
@override
void initState() {
super.initState();
isSubmitting = false;
StripePayment.setOptions(
StripeOptions(publishableKey: widget.paymentPlatform.publishableKey,
merchantId: widget.paymentPlatform.merchantId,
androidPayMode: 'test')
);
}
_paymentWithPaymentMethod(BuildContext context) async {
Utils.stripePaymentIntent(widget.order, widget.stripePaymentMethod.customerId,
widget.stripePaymentMethod.paymentMethodId,
widget.stripePaymentMethod.paymentMethodType, (response){
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
StripePayment.confirmPaymentIntent(
PaymentIntent(
clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET],
paymentMethodId: response.data['payment_method'],
),
).then((paymentIntentResult) {
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
Utils.stripeChargedSuccess(widget.order,
widget.stripePaymentMethod.paymentMethodId,
paymentIntentResult.paymentIntentId,
(response) {
eventBus.fire(OnOrderUpdated());
Routes.router.navigateTo(context, '/orderdetail/${widget
.order.id}', replace: true);
},
(showErrorDialog)
);
} else {
showErrorDialog(Exception('Unknown error'));
}
}).catchError(showErrorDialog);
}
}, (showErrorDialog));
isSubmitting = true;
Utils.showSubmitDialog(context);
}
_paymentRequestWithCardForm(BuildContext context) async {
StripePayment.paymentRequestWithCardForm(
CardFormPaymentRequest()
).then((paymentMethod) {
Utils.stripePaymentIntent(widget.order, null, paymentMethod.id, paymentMethod.type, (response){
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
StripePayment.confirmPaymentIntent(
PaymentIntent(
clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET],
paymentMethodId: response.data['payment_method'],
),
).then((paymentIntentResult) {
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
Utils.stripeChargedSuccess(widget.order,
paymentMethod.id,
paymentIntentResult.paymentIntentId,
(response) {
eventBus.fire(OnOrderUpdated());
Routes.router.navigateTo(context, '/orderdetail/${widget
.order.id}', replace: true);
},
(showErrorDialog)
);
} else {
showErrorDialog(Exception('Unknown error'));
}
}).catchError(showErrorDialog);
}
}, (showErrorDialog),
cardBrand: paymentMethod.card.brand,
cardCountry: paymentMethod.card.country,
cardExpMonth: paymentMethod.card.expMonth,
cardExpYear: paymentMethod.card.expYear,
cardFunding: paymentMethod.card.funding,
cardLast4: paymentMethod.card.last4,
);
}).catchError(showErrorDialog);
isSubmitting = true;
Utils.showSubmitDialog(context);
}
void showErrorDialog(dynamic error) {
if (isSubmitting) {
Navigator.of(context).pop();
isSubmitting = false;
}
Utils.showMessageDialog(context, error, onOk: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
});
}
}

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class Style {
static final fontSize = 15.0;
static final primaryColor = const Color(0xFF3190e8);
static final backgroundColor = const Color(0xFFFFFFFF);
static final emptyBackgroundColor = const Color(0xFFF5F5F5);
static final borderColor = const Color(0xFFE4E4E4);
static final gPadding = 16.0;
static final textStyle = new TextStyle(
color: const Color(0xFF333333),
fontSize: fontSize,
);
static BoxDecoration testDecoration(Color color) {
return new BoxDecoration(
border: new Border.all(color: color),
);
}
}

View File

@@ -2,6 +2,7 @@
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_wisetronic/utils/utils.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../routes.dart';
@@ -21,6 +22,8 @@ class TextLink extends StatelessWidget {
final bool rootNavigator;
final TransitionType transition;
final bool closeDrawer;
final bool isEmail;
final bool isPhone;
TextLink(this.title, this.url, {
this.color,
this.paddingHorizontal,
@@ -34,13 +37,17 @@ class TextLink extends StatelessWidget {
bool rootNavigator,
this.transition,
bool closeDrawer,
bool isEmail,
bool isPhone,
}) :
isLink = isLink ?? false,
replace = replace ?? false,
clearStack = clearStack ?? false,
maintainState = maintainState ?? true,
rootNavigator = rootNavigator ?? false,
closeDrawer = closeDrawer ?? false;
closeDrawer = closeDrawer ?? false,
isEmail = isEmail ?? false,
isPhone = isPhone ?? false;
@override
Widget build(BuildContext context) {
@@ -69,22 +76,28 @@ class TextLink extends StatelessWidget {
),
),
onTap: () async {
if (!isLink) {
if (closeDrawer) {
Routes.router.pop(context);
}
Routes.router.navigateTo(
context, url,
replace: replace,
clearStack: clearStack,
maintainState: maintainState,
rootNavigator: rootNavigator,
);
} else {
if (await canLaunch(url)) {
await launch(url);
if (selected == null || !selected) {
if (isEmail) {
Utils.openEmail(url);
} else if (isPhone) {
Utils.callPhone(url);
} else if (!isLink) {
if (closeDrawer) {
Routes.router.pop(context);
}
Routes.router.navigateTo(
context, url,
replace: replace,
clearStack: clearStack,
maintainState: maintainState,
rootNavigator: rootNavigator,
);
} else {
throw 'Could not launch $url';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
}
},