Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
909fdf46d6 | ||
|
|
e7ce0f7bae |
@@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
|
||||||
class ImageViewer extends StatefulWidget {
|
class ImageViewer extends StatefulWidget {
|
||||||
final Key key;
|
|
||||||
final ImageProvider imageProvider;
|
final ImageProvider imageProvider;
|
||||||
|
|
||||||
ImageViewer(this.imageProvider, {this.key}) :
|
ImageViewer(this.imageProvider, {Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -33,18 +33,15 @@ AlertDialog logoutDialog(BuildContext context) {
|
|||||||
.yes_i_am_sure),
|
.yes_i_am_sure),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Utils.getBox().then((box) {
|
Utils.prefs?.remove(Constants.KEY_USER_ID);
|
||||||
box.delete(Constants.KEY_USER_ID);
|
Utils.prefs?.remove(Constants.KEY_ACCESS_TOKEN);
|
||||||
box.delete(
|
store.dispatch(
|
||||||
Constants.KEY_ACCESS_TOKEN);
|
new UpdateCurrentUser(null));
|
||||||
store.dispatch(
|
eventBus.fire(
|
||||||
new UpdateCurrentUser(null));
|
new OnCurrentUserUpdated());
|
||||||
eventBus.fire(
|
Routes.router.navigateTo(
|
||||||
new OnCurrentUserUpdated());
|
context, '/', replace: true,
|
||||||
Routes.router.navigateTo(
|
clearStack: true);
|
||||||
context, '/', replace: true,
|
|
||||||
clearStack: true);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class OnProductWillAddToCart {
|
|||||||
double price;
|
double price;
|
||||||
String description;
|
String description;
|
||||||
Business business;
|
Business business;
|
||||||
GlobalKey buttonKey;
|
GlobalKey? buttonKey;
|
||||||
OnProductWillAddToCart(this.product, this.selections, this.price, this.description, this.business, {this.buttonKey});
|
OnProductWillAddToCart(this.product, this.selections, this.price, this.description, this.business, {this.buttonKey});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-44
@@ -1,9 +1,4 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:catcher/catcher.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_redux/flutter_redux.dart';
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
import 'package:flutter_wisetronic/pages/buy_service.dart';
|
import 'package:flutter_wisetronic/pages/buy_service.dart';
|
||||||
@@ -14,12 +9,8 @@ import 'pages/create_online_store_1.dart';
|
|||||||
import 'pages/download.dart';
|
import 'pages/download.dart';
|
||||||
import 'pages/me.dart';
|
import 'pages/me.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
import 'package:splashscreen/splashscreen.dart';
|
|
||||||
// import 'package:uni_links/uni_links.dart';
|
|
||||||
|
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
import 'events/eventbus.dart';
|
|
||||||
import 'events/events.dart';
|
|
||||||
import 'generated/l10n.dart';
|
import 'generated/l10n.dart';
|
||||||
import 'pages/home.dart';
|
import 'pages/home.dart';
|
||||||
import 'pages/plain_page.dart';
|
import 'pages/plain_page.dart';
|
||||||
@@ -34,20 +25,14 @@ import 'utils/utils.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
// Enable configureApp will cause route problem.
|
|
||||||
// configureApp();
|
|
||||||
setPathUrlStrategy();
|
setPathUrlStrategy();
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
String _to = '/';
|
|
||||||
|
|
||||||
MyApp() {
|
MyApp() {
|
||||||
Routes.configure();
|
Routes.configure();
|
||||||
Utils().init();
|
Utils().init();
|
||||||
Util().init();
|
|
||||||
// getCurrentPosition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCurrentPosition() async {
|
static getCurrentPosition() async {
|
||||||
@@ -95,27 +80,8 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
Widget _buildBody() {
|
|
||||||
return SplashScreen(
|
|
||||||
seconds: 5,
|
|
||||||
routeName: _to,
|
|
||||||
navigateAfterSeconds: Home(title: Constants.APP_TITLE,),
|
|
||||||
styleTextUnderTheLoader: new TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 20.0,
|
|
||||||
),
|
|
||||||
imageBackground: (Image.network(Constants.BASE_API_URL + 'gallery/get-minimanager-splash/')).image,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
onClick: () {
|
|
||||||
|
|
||||||
},
|
|
||||||
loaderColor: Colors.blue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: Constants.DEBUG,
|
debugShowCheckedModeBanner: Constants.DEBUG,
|
||||||
navigatorKey: kIsWeb ? null : Catcher.navigatorKey,
|
|
||||||
localizationsDelegates: [
|
localizationsDelegates: [
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
GlobalWidgetsLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate,
|
||||||
@@ -124,20 +90,26 @@ class MyApp extends StatelessWidget {
|
|||||||
S.delegate,
|
S.delegate,
|
||||||
],
|
],
|
||||||
supportedLocales: S.delegate.supportedLocales,
|
supportedLocales: S.delegate.supportedLocales,
|
||||||
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) {
|
localeResolutionCallback: (Locale? locale, Iterable<Locale>? supportedLocales) {
|
||||||
print('Language code: ${locale.languageCode}, Country code: ${locale.countryCode}');
|
print('Language code: ${locale?.languageCode}, Country code: ${locale?.countryCode}');
|
||||||
for (final supportedLocale in supportedLocales) {
|
if (locale != null && supportedLocales != null) {
|
||||||
if (supportedLocale.languageCode == locale.languageCode) {
|
for (final supportedLocale in supportedLocales) {
|
||||||
store.dispatch(UpdateLocale(locale));
|
if (supportedLocale.languageCode == locale.languageCode) {
|
||||||
return supportedLocale;
|
store.dispatch(UpdateLocale(locale));
|
||||||
|
return supportedLocale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
store.dispatch(UpdateLocale(supportedLocales.first));
|
||||||
store.dispatch(UpdateLocale(supportedLocales.first));
|
|
||||||
return supportedLocales.first;
|
return supportedLocales.first;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
onGenerateRoute: (RouteSettings settings) {
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
final List<String> pathElements = settings.name.split('/');
|
final uri = Uri.parse(settings.name!);
|
||||||
print('path elements: $pathElements');
|
final path = uri.path;
|
||||||
|
print('path: $path');
|
||||||
|
final List<String> pathElements = path.split('/');
|
||||||
|
print('path elements --> $pathElements');
|
||||||
if (pathElements[0] != '') {
|
if (pathElements[0] != '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -1,21 +1,21 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class Address {
|
class Address {
|
||||||
int id;
|
int? id;
|
||||||
String addressLine1;
|
String? addressLine1;
|
||||||
String addressLine2;
|
String? addressLine2;
|
||||||
String city;
|
String? city;
|
||||||
String state;
|
String? state;
|
||||||
String country;
|
String? country;
|
||||||
String zip;
|
String? zip;
|
||||||
String phone;
|
String? phone;
|
||||||
String fax;
|
String? fax;
|
||||||
String email;
|
String? email;
|
||||||
String contactName;
|
String? contactName;
|
||||||
int gender;
|
int? gender;
|
||||||
String lat;
|
String? lat;
|
||||||
String lng;
|
String? lng;
|
||||||
String fullAddress;
|
String? fullAddress;
|
||||||
|
|
||||||
Address.fromJson(Map<String, dynamic> json)
|
Address.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class Business {
|
|||||||
String chaseXLogin;
|
String chaseXLogin;
|
||||||
String chaseTransactionKey;
|
String chaseTransactionKey;
|
||||||
String chaseResponseKey;
|
String chaseResponseKey;
|
||||||
DistanceInfo distanceInfo;
|
DistanceInfo? distanceInfo;
|
||||||
int commentsCount;
|
int commentsCount;
|
||||||
bool forceClose;
|
bool forceClose;
|
||||||
bool isPublic;
|
bool isPublic;
|
||||||
|
|||||||
+19
-19
@@ -7,18 +7,18 @@ import 'cart_line_item.dart';
|
|||||||
import 'extra_fee.dart';
|
import 'extra_fee.dart';
|
||||||
|
|
||||||
class CartInfo {
|
class CartInfo {
|
||||||
int id;
|
int? id;
|
||||||
double originPrice;
|
double? originPrice;
|
||||||
double discountPrice;
|
double? discountPrice;
|
||||||
double totalPrice;
|
double? totalPrice;
|
||||||
Business businessInfo;
|
Business? businessInfo;
|
||||||
List<CartLineItem> productList;
|
List<CartLineItem>? productList;
|
||||||
List<ExtraFee> extraFeeList;
|
List<ExtraFee>? extraFeeList;
|
||||||
List<ExtraFee> discountList;
|
List<ExtraFee>? discountList;
|
||||||
int deliveryMethod;
|
int? deliveryMethod;
|
||||||
String shippingMethod;
|
String? shippingMethod;
|
||||||
double amountPaid;
|
double? amountPaid;
|
||||||
String extraData;
|
String? extraData;
|
||||||
|
|
||||||
CartInfo.fromJson(Map<String, dynamic> json)
|
CartInfo.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
@@ -53,7 +53,7 @@ class CartInfo {
|
|||||||
if (productList == null) {
|
if (productList == null) {
|
||||||
productList = [item];
|
productList = [item];
|
||||||
} else {
|
} else {
|
||||||
productList.add(item);
|
productList!.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -74,19 +74,19 @@ class CartInfo {
|
|||||||
originPrice = 0.0;
|
originPrice = 0.0;
|
||||||
discountPrice = 0.0;
|
discountPrice = 0.0;
|
||||||
totalPrice = 0.0;
|
totalPrice = 0.0;
|
||||||
for (var i = 0; i < productList.length; i++) {
|
for (var i = 0; i < productList!.length; i++) {
|
||||||
newTotal += productList[i].getTotalPrice();
|
newTotal += productList![i].getTotalPrice();
|
||||||
}
|
}
|
||||||
originPrice = newTotal;
|
originPrice = newTotal;
|
||||||
totalPrice = newTotal;
|
totalPrice = newTotal;
|
||||||
return totalPrice;
|
return totalPrice!;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getProductListTotalQuantity() {
|
int getProductListTotalQuantity() {
|
||||||
int qty = 0;
|
int qty = 0;
|
||||||
if (productList != null && productList.length > 0) {
|
if (productList != null && productList!.length > 0) {
|
||||||
for (var i = 0; i < productList.length; i++) {
|
for (var i = 0; i < productList!.length; i++) {
|
||||||
qty += productList[i].quantity.round();
|
qty += productList![i].quantity!.round();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return qty;
|
return qty;
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import 'package:uuid/uuid.dart';
|
|||||||
import 'product.dart';
|
import 'product.dart';
|
||||||
|
|
||||||
class CartLineItem {
|
class CartLineItem {
|
||||||
int id;
|
int? id;
|
||||||
double unitPrice;
|
double? unitPrice;
|
||||||
double totalPrice;
|
double? totalPrice;
|
||||||
Product product;
|
Product? product;
|
||||||
String name;
|
String? name;
|
||||||
String description;
|
String? description;
|
||||||
double quantity;
|
double? quantity;
|
||||||
String uuid;
|
String? uuid;
|
||||||
String parentUuid;
|
String? parentUuid;
|
||||||
|
|
||||||
CartLineItem() {
|
CartLineItem() {
|
||||||
uuid = Uuid().v4();
|
uuid = Uuid().v4();
|
||||||
@@ -48,7 +48,7 @@ class CartLineItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double getTotalPrice() {
|
double getTotalPrice() {
|
||||||
totalPrice = unitPrice * quantity;
|
totalPrice = unitPrice! * quantity!;
|
||||||
return totalPrice;
|
return totalPrice!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ class Coupon {
|
|||||||
String description;
|
String description;
|
||||||
String expirationDate;
|
String expirationDate;
|
||||||
double minAmount;
|
double minAmount;
|
||||||
Business store;
|
Business? store;
|
||||||
double valueAmount;
|
double valueAmount;
|
||||||
bool isPercentage;
|
bool isPercentage;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import 'dart:convert';
|
|||||||
import 'text_value.dart';
|
import 'text_value.dart';
|
||||||
|
|
||||||
class DistanceInfo {
|
class DistanceInfo {
|
||||||
TextValue duration;
|
TextValue? duration;
|
||||||
TextValue distance;
|
TextValue? distance;
|
||||||
String status;
|
String? status;
|
||||||
|
|
||||||
DistanceInfo.fromJson(Map<String, dynamic> json)
|
DistanceInfo.fromJson(Map<String, dynamic> json)
|
||||||
: duration = json['duration'] != null ? TextValue.fromJson(json['duration']) : null,
|
: duration = json['duration'] != null ? TextValue.fromJson(json['duration']) : null,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class Order {
|
|||||||
int id;
|
int id;
|
||||||
int userId;
|
int userId;
|
||||||
int businessId;
|
int businessId;
|
||||||
Business businessInfo;
|
Business? businessInfo;
|
||||||
CartInfo cartInfo;
|
CartInfo? cartInfo;
|
||||||
int status;
|
int status;
|
||||||
double originPrice;
|
double originPrice;
|
||||||
double discountPrice;
|
double discountPrice;
|
||||||
@@ -23,7 +23,7 @@ class Order {
|
|||||||
String consignee;
|
String consignee;
|
||||||
String phone;
|
String phone;
|
||||||
String address;
|
String address;
|
||||||
Address shippingAddress;
|
Address? shippingAddress;
|
||||||
int payMethod;
|
int payMethod;
|
||||||
String remark;
|
String remark;
|
||||||
String orderNum;
|
String orderNum;
|
||||||
@@ -33,11 +33,11 @@ class Order {
|
|||||||
String paymentStatus; // UNPAID, PAID, PARTIALPAID
|
String paymentStatus; // UNPAID, PAID, PARTIALPAID
|
||||||
double amountPaid;
|
double amountPaid;
|
||||||
List<Fulfillment> fulfillments;
|
List<Fulfillment> fulfillments;
|
||||||
String extraData;
|
String? extraData;
|
||||||
bool hasComment;
|
bool hasComment;
|
||||||
double fee;
|
double fee;
|
||||||
UserPosition shipperPosition;
|
UserPosition shipperPosition;
|
||||||
DistanceInfo deliveryDistance;
|
DistanceInfo? deliveryDistance;
|
||||||
|
|
||||||
Order.fromJson(Map<String, dynamic> json)
|
Order.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
@@ -100,8 +100,10 @@ class Order {
|
|||||||
|
|
||||||
double getSubtotal() {
|
double getSubtotal() {
|
||||||
double subtotal = 0.0;
|
double subtotal = 0.0;
|
||||||
for (CartLineItem lineItem in cartInfo.productList) {
|
if (cartInfo != null) {
|
||||||
subtotal += lineItem.getTotalPrice();
|
for (CartLineItem lineItem in cartInfo!.productList!) {
|
||||||
|
subtotal += lineItem.getTotalPrice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return subtotal;
|
return subtotal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import '../generated/l10n.dart';
|
import '../generated/l10n.dart';
|
||||||
|
|
||||||
class PaymentPlatform {
|
class PaymentPlatform {
|
||||||
int id;
|
int? id;
|
||||||
String name;
|
String? name;
|
||||||
String code;
|
String? code;
|
||||||
String method;
|
String? method;
|
||||||
String icon;
|
String? icon;
|
||||||
String xLogin;
|
String? xLogin;
|
||||||
String transactionKey;
|
String? transactionKey;
|
||||||
String responseKey;
|
String? responseKey;
|
||||||
String squareAppId;
|
String? squareAppId;
|
||||||
String squareAccessToken;
|
String? squareAccessToken;
|
||||||
String squareLocationId;
|
String? squareLocationId;
|
||||||
String publishableKey;
|
String? publishableKey;
|
||||||
String merchantId;
|
String? merchantId;
|
||||||
|
|
||||||
PaymentPlatform(this.code);
|
PaymentPlatform(this.code);
|
||||||
|
|
||||||
@@ -57,16 +57,12 @@ class PaymentPlatform {
|
|||||||
switch(code) {
|
switch(code) {
|
||||||
case 'chase':
|
case 'chase':
|
||||||
return S.of(context).credit_debit_card;
|
return S.of(context).credit_debit_card;
|
||||||
break;
|
|
||||||
case 'web-credit-card':
|
case 'web-credit-card':
|
||||||
return S.of(context).credit_card;
|
return S.of(context).credit_card;
|
||||||
break;
|
|
||||||
case 'ALIPAY':
|
case 'ALIPAY':
|
||||||
return S.of(context).alipay;
|
return S.of(context).alipay;
|
||||||
break;
|
|
||||||
case 'WECHATPAY':
|
case 'WECHATPAY':
|
||||||
return S.of(context).wechatpay;
|
return S.of(context).wechatpay;
|
||||||
break;
|
|
||||||
case 'paypal':
|
case 'paypal':
|
||||||
return S.of(context).paypal;
|
return S.of(context).paypal;
|
||||||
case 'payondeliverypickup':
|
case 'payondeliverypickup':
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class Position {
|
|||||||
final double latitude;
|
final double latitude;
|
||||||
final double longitude;
|
final double longitude;
|
||||||
|
|
||||||
Position({this.latitude, this.longitude});
|
Position({required this.latitude, required this.longitude});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) {
|
bool operator ==(other) {
|
||||||
|
|||||||
+17
-17
@@ -5,23 +5,23 @@ import 'Subproduct.dart';
|
|||||||
import 'product_attribute.dart';
|
import 'product_attribute.dart';
|
||||||
|
|
||||||
class Product {
|
class Product {
|
||||||
int id;
|
int? id;
|
||||||
int businessId;
|
int? businessId;
|
||||||
int categoryId;
|
int? categoryId;
|
||||||
String name;
|
String? name;
|
||||||
double price;
|
double? price;
|
||||||
double regularPrice;
|
double? regularPrice;
|
||||||
String description;
|
String? description;
|
||||||
String detailDescription;
|
String? detailDescription;
|
||||||
String imagePath;
|
String? imagePath;
|
||||||
String secondImagePath;
|
String? secondImagePath;
|
||||||
double monthSales;
|
double? monthSales;
|
||||||
int rate;
|
int? rate;
|
||||||
double leftNum;
|
double? leftNum;
|
||||||
List<ProductAttribute> productAttributes;
|
List<ProductAttribute>? productAttributes;
|
||||||
bool nonInventory;
|
bool? nonInventory;
|
||||||
String extraData;
|
String? extraData;
|
||||||
List<Subproduct> subproducts;
|
List<Subproduct>? subproducts;
|
||||||
|
|
||||||
Product(int id, int businessId, String name, double price, String description,
|
Product(int id, int businessId, String name, double price, String description,
|
||||||
String imagePath, List<ProductAttribute> productAttributes) {
|
String imagePath, List<ProductAttribute> productAttributes) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class ProductAttribute {
|
|||||||
bool singleSelection;
|
bool singleSelection;
|
||||||
bool byQuantity;
|
bool byQuantity;
|
||||||
bool required;
|
bool required;
|
||||||
String extra;
|
String? extra;
|
||||||
bool disabled;
|
bool disabled;
|
||||||
|
|
||||||
ProductAttribute.fromJson(Map<String, dynamic> json)
|
ProductAttribute.fromJson(Map<String, dynamic> json)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'dart:convert';
|
|||||||
import 'waiting_line_item.dart';
|
import 'waiting_line_item.dart';
|
||||||
|
|
||||||
class WaitingLine {
|
class WaitingLine {
|
||||||
List<WaitingLineItem> waitingLines;
|
List<WaitingLineItem>? waitingLines;
|
||||||
String storeSn;
|
String? storeSn;
|
||||||
|
|
||||||
WaitingLine.fromJson(Map<String, dynamic> json)
|
WaitingLine.fromJson(Map<String, dynamic> json)
|
||||||
: waitingLines = (json['waiting_lines'] as List).map((i) => WaitingLineItem.fromJson(i)).toList(),
|
: waitingLines = (json['waiting_lines'] as List).map((i) => WaitingLineItem.fromJson(i)).toList(),
|
||||||
|
|||||||
@@ -8,19 +8,16 @@ import '../models/product.dart';
|
|||||||
class AttributeSelection extends StatelessWidget {
|
class AttributeSelection extends StatelessWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
final Business business;
|
final Business business;
|
||||||
final GlobalKey startKey;
|
final GlobalKey? startKey;
|
||||||
|
|
||||||
const AttributeSelection({Key key, this.product, this.business, this.startKey}) : super(key: key);
|
const AttributeSelection({Key? key, required this.product, required this.business, this.startKey}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ScreenTypeLayout.builder(
|
||||||
builder: (context, sizingInformation) =>
|
mobile: (context) => MobileAttributeSelection(product: product, business: business, startKey: startKey),
|
||||||
ScreenTypeLayout(
|
tablet: (context) => MobileAttributeSelection(product: product, business: business, startKey: startKey),
|
||||||
mobile: MobileAttributeSelection(product: product, business: business, startKey: startKey,),
|
desktop: (context) => MobileAttributeSelection(product: product, business: business, startKey: startKey),
|
||||||
tablet: MobileAttributeSelection(product: product, business: business, startKey: startKey,),
|
|
||||||
desktop: MobileAttributeSelection(product: product, business: business, startKey: startKey,),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-12
@@ -8,24 +8,20 @@ import '../widgets/desktop/desktop_blog.dart';
|
|||||||
import '../widgets/mobile/mobile_blog.dart';
|
import '../widgets/mobile/mobile_blog.dart';
|
||||||
|
|
||||||
class Blog extends StatelessWidget {
|
class Blog extends StatelessWidget {
|
||||||
final Key key;
|
|
||||||
final int businessId;
|
final int businessId;
|
||||||
|
|
||||||
const Blog({this.key, int businessId}) :
|
const Blog({Key? key, required int businessId}) :
|
||||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ScreenTypeLayout.builder(
|
||||||
builder: (context, sizingInformation) =>
|
mobile: (context) => MobileBlog(businessId: businessId),
|
||||||
ScreenTypeLayout(
|
tablet: (context) => DesktopBlog(businessId: businessId),
|
||||||
mobile: MobileBlog(businessId: businessId,),
|
desktop: (context) => Scrollbar(
|
||||||
tablet: DesktopBlog(businessId: businessId,),
|
thumbVisibility: true,
|
||||||
desktop: Scrollbar(
|
child: DesktopBlog(businessId: businessId),
|
||||||
isAlwaysShown: true,
|
),
|
||||||
child: DesktopBlog(businessId: businessId,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ import '../widgets/mobile/mobile_renew_license.dart';
|
|||||||
class BuyService extends StatefulWidget {
|
class BuyService extends StatefulWidget {
|
||||||
final int gid;
|
final int gid;
|
||||||
final String serviceName;
|
final String serviceName;
|
||||||
final String domain;
|
final String? domain;
|
||||||
final int sid;
|
final int? sid;
|
||||||
|
|
||||||
const BuyService(this.gid, this.serviceName, {Key key, this.domain, this.sid = 0}) :
|
const BuyService(this.gid, this.serviceName, {Key? key, this.domain, this.sid = 0}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -30,7 +30,7 @@ class BuyService extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BuyServiceState extends State<BuyService> {
|
class BuyServiceState extends State<BuyService> {
|
||||||
Map<String, dynamic> data;
|
Map<String, dynamic>? data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -48,13 +48,10 @@ class BuyServiceState extends State<BuyService> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponsiveBuilder(
|
return ScreenTypeLayout.builder(
|
||||||
builder: (context, sizingInformation) =>
|
mobile: (context) => MobileBuyService(data),
|
||||||
ScreenTypeLayout(
|
tablet: (context) => DesktopBuyService(data),
|
||||||
mobile: MobileBuyService(data),
|
desktop: (context) => DesktopBuyService(data),
|
||||||
tablet: DesktopBuyService(data),
|
|
||||||
desktop: DesktopBuyService(data),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +70,7 @@ class BuyServiceState extends State<BuyService> {
|
|||||||
}
|
}
|
||||||
).then((value) {
|
).then((value) {
|
||||||
data = value;
|
data = value;
|
||||||
data['domain'] = widget.domain;
|
data?['domain'] = widget.domain;
|
||||||
print('data: $data');
|
print('data: $data');
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}).onError((error, stackTrace) {
|
}).onError((error, stackTrace) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import '../widgets/mobile/mobile_change_mobile_or_email.dart';
|
|||||||
class ChangeMobileOrEmail extends StatelessWidget {
|
class ChangeMobileOrEmail extends StatelessWidget {
|
||||||
final bool isMobile;
|
final bool isMobile;
|
||||||
|
|
||||||
const ChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key);
|
const ChangeMobileOrEmail(this.isMobile, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -31,15 +31,15 @@ class ChangeMobileOrEmail extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileChangeMobileOrEmail(isMobile),
|
mobile: (context) => MobileChangeMobileOrEmail(isMobile),
|
||||||
tablet: DesktopChangeMobileOrEmail(isMobile),
|
tablet: (context) => DesktopChangeMobileOrEmail(isMobile),
|
||||||
desktop: DesktopChangeMobileOrEmail(isMobile),
|
desktop: (context) => DesktopChangeMobileOrEmail(isMobile),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ class ChangePassword extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileChangePassword(),
|
mobile: (context) => MobileChangePassword(),
|
||||||
tablet: DesktopChangePassword(),
|
tablet: (context) => DesktopChangePassword(),
|
||||||
desktop: DesktopChangePassword(),
|
desktop: (context) => DesktopChangePassword(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import '../widgets/mobile/mobile_checkout.dart';
|
|||||||
class Checkout extends StatelessWidget {
|
class Checkout extends StatelessWidget {
|
||||||
final int businessId;
|
final int businessId;
|
||||||
|
|
||||||
const Checkout(this.businessId, {Key key}) :
|
const Checkout(this.businessId, {Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,10 +20,10 @@ class Checkout extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileCheckout(businessId),
|
mobile: (context) => MobileCheckout(businessId),
|
||||||
tablet: DesktopCheckout(businessId),
|
tablet: (context) => DesktopCheckout(businessId),
|
||||||
desktop: DesktopCheckout(businessId),
|
desktop: (context) => DesktopCheckout(businessId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import '../widgets/mobile/mobile_contact_us.dart';
|
|||||||
class ContactUs extends StatefulWidget {
|
class ContactUs extends StatefulWidget {
|
||||||
final int businessId;
|
final int businessId;
|
||||||
|
|
||||||
const ContactUs({this.businessId, Key key}) :
|
const ContactUs({required this.businessId, Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -22,7 +22,7 @@ class ContactUs extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ContactUsState extends State<ContactUs> {
|
class ContactUsState extends State<ContactUs> {
|
||||||
Business business;
|
Business? business;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -42,10 +42,10 @@ class ContactUsState extends State<ContactUs> {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileContactUs(business),
|
mobile: (context) => MobileContactUs(business!),
|
||||||
tablet: DesktopContactUs(business),
|
tablet: (context) => DesktopContactUs(business!),
|
||||||
desktop: DesktopContactUs(business),
|
desktop: (context) => DesktopContactUs(business!),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import '../widgets/mobile/mobile_coupons.dart';
|
|||||||
class Coupons extends StatelessWidget {
|
class Coupons extends StatelessWidget {
|
||||||
final int contactId;
|
final int contactId;
|
||||||
|
|
||||||
const Coupons(this.contactId, {Key key}) :
|
const Coupons(this.contactId, {Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ class Coupons extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileCoupons(contactId,),
|
mobile: (context) => MobileCoupons(contactId,),
|
||||||
tablet: DesktopCoupons(contactId,),
|
tablet: (context) => DesktopCoupons(contactId,),
|
||||||
desktop: DesktopCoupons(contactId,),
|
desktop: (context) => DesktopCoupons(contactId,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import '../widgets/mobile/create_online_store_1.dart' as mobile;
|
|||||||
|
|
||||||
class CreateOnlineStore1 extends StatefulWidget {
|
class CreateOnlineStore1 extends StatefulWidget {
|
||||||
|
|
||||||
const CreateOnlineStore1({Key key}) :
|
const CreateOnlineStore1({Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,10 +33,10 @@ class CreateOnlineStore1State extends State<CreateOnlineStore1> {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: mobile.CreateOnlineStore1(Constants.BUSINESS_ID),
|
mobile: (context) => mobile.CreateOnlineStore1(Constants.BUSINESS_ID),
|
||||||
tablet: desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
|
tablet: (context) => desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
|
||||||
desktop: desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
|
desktop: (context) => desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -30,7 +30,7 @@ class Download extends StatefulWidget {
|
|||||||
|
|
||||||
class DownloadState extends State<Download> {
|
class DownloadState extends State<Download> {
|
||||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
Map<String, dynamic> data;
|
Map<String, dynamic>? data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -57,18 +57,18 @@ class DownloadState extends State<Download> {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null,
|
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileDownloadApps(data),
|
mobile: (context) => MobileDownloadApps(data!),
|
||||||
tablet: DesktopDownloadApps(data),
|
tablet: (context) => DesktopDownloadApps(data!),
|
||||||
desktop: Scrollbar(
|
desktop: (context) => Scrollbar(
|
||||||
isAlwaysShown: true,
|
thumbVisibility: true,
|
||||||
child: DesktopDownloadApps(data),
|
child: DesktopDownloadApps(data!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(),
|
mobile: (context) => MobileBottomNav(),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -79,7 +79,7 @@ class DownloadState extends State<Download> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
eventBus.on<OpenDrawer>().listen((event) {
|
eventBus.on<OpenDrawer>().listen((event) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_scaffoldKey.currentState.openDrawer();
|
_scaffoldKey.currentState?.openDrawer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_loadData();
|
_loadData();
|
||||||
|
|||||||
@@ -13,20 +13,19 @@ import '../widgets/general/navigationbar.dart';
|
|||||||
import '../widgets/mobile/MobileBottomNav.dart';
|
import '../widgets/mobile/MobileBottomNav.dart';
|
||||||
|
|
||||||
class EditAddress extends StatelessWidget {
|
class EditAddress extends StatelessWidget {
|
||||||
final Key key;
|
final Address? address;
|
||||||
final Address address;
|
final int? businessId;
|
||||||
final int businessId;
|
const EditAddress(this.address, {Key? key, int? businessId}) :
|
||||||
const EditAddress(this.address, {this.key, int businessId}) :
|
|
||||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileEditAddress(address, businessId: businessId,),
|
mobile: (context) => MobileEditAddress(address, businessId: businessId,),
|
||||||
tablet: DesktopEditAddress(address, businessId: businessId,),
|
tablet: (context) => DesktopEditAddress(address, businessId: businessId,),
|
||||||
desktop: DesktopEditAddress(address, businessId: businessId,),
|
desktop: (context) => DesktopEditAddress(address, businessId: businessId,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import '../widgets/general/navigationbar.dart';
|
|||||||
import '../widgets/mobile/MobileBottomNav.dart';
|
import '../widgets/mobile/MobileBottomNav.dart';
|
||||||
|
|
||||||
class ForgotPassword extends StatelessWidget {
|
class ForgotPassword extends StatelessWidget {
|
||||||
const ForgotPassword({Key key}) : super(key: key);
|
const ForgotPassword({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,15 +28,15 @@ class ForgotPassword extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileForgotPassword(),
|
mobile: (context) => MobileForgotPassword(),
|
||||||
tablet: DesktopForgotPassword(),
|
tablet: (context) => DesktopForgotPassword(),
|
||||||
desktop: DesktopForgotPassword(),
|
desktop: (context) => DesktopForgotPassword(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+18
-16
@@ -34,7 +34,7 @@ import '../widgets/mobile/mobile_navigation_drawer.dart';
|
|||||||
class Home extends StatefulWidget {
|
class Home extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
Home({Key key, this.title = ''}) : super(key: key);
|
Home({Key? key, this.title = ''}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -46,11 +46,11 @@ class Home extends StatefulWidget {
|
|||||||
class HomeState extends State<Home> {
|
class HomeState extends State<Home> {
|
||||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
List<Gallery> galleries = [];
|
List<Gallery> galleries = [];
|
||||||
String content1Message;
|
String? content1Message;
|
||||||
Map<String, dynamic> content2;
|
Map<String, dynamic>? content2;
|
||||||
|
|
||||||
StreamSubscription _sub;
|
StreamSubscription? _sub;
|
||||||
Uri _latestUri;
|
Uri? _latestUri;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -74,8 +74,8 @@ class HomeState extends State<Home> {
|
|||||||
appBar: MiniNavigationBar(),
|
appBar: MiniNavigationBar(),
|
||||||
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null,
|
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null,
|
||||||
body: DoubleBackToCloseAppWrapper(
|
body: DoubleBackToCloseAppWrapper(
|
||||||
child: ScreenTypeLayout(
|
child: ScreenTypeLayout.builder(
|
||||||
mobile: SingleChildScrollView(
|
mobile: (context) => SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -86,7 +86,7 @@ class HomeState extends State<Home> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tablet: SingleChildScrollView(
|
tablet: (context) => SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -98,8 +98,8 @@ class HomeState extends State<Home> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
desktop: Scrollbar(
|
desktop: (context) => Scrollbar(
|
||||||
isAlwaysShown: true,
|
thumbVisibility: true,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -115,10 +115,10 @@ class HomeState extends State<Home> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -136,7 +136,7 @@ class HomeState extends State<Home> {
|
|||||||
_loadData();
|
_loadData();
|
||||||
eventBus.on<OpenDrawer>().listen((event) {
|
eventBus.on<OpenDrawer>().listen((event) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_scaffoldKey.currentState.openDrawer();
|
_scaffoldKey.currentState?.openDrawer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
@@ -163,7 +163,9 @@ class HomeState extends State<Home> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_latestUri = Uri.base;
|
_latestUri = Uri.base;
|
||||||
print('uri base $_latestUri');
|
print('uri base $_latestUri');
|
||||||
eventBus.fire(OnGotDeepLinkUri(_latestUri));
|
if (_latestUri != null) {
|
||||||
|
eventBus.fire(OnGotDeepLinkUri(_latestUri!));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// _sub = getUriLinksStream().listen((Uri uri) {
|
// _sub = getUriLinksStream().listen((Uri uri) {
|
||||||
// print('_sub should get uri $uri');
|
// print('_sub should get uri $uri');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_igoshow_learn_more.dart';
|
import '../widgets/mobile/mobile_igoshow_learn_more.dart';
|
||||||
|
|
||||||
class IGoShowLearnMore extends StatefulWidget {
|
class IGoShowLearnMore extends StatefulWidget {
|
||||||
const IGoShowLearnMore({Key key}) : super(key: key);
|
const IGoShowLearnMore({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -26,7 +26,7 @@ class IGoShowLearnMore extends StatefulWidget {
|
|||||||
|
|
||||||
class IGoShowLearnMoreState extends State<IGoShowLearnMore> {
|
class IGoShowLearnMoreState extends State<IGoShowLearnMore> {
|
||||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
Map<String, dynamic> data;
|
Map<String, dynamic>? data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -55,15 +55,15 @@ class IGoShowLearnMoreState extends State<IGoShowLearnMore> {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileiGoShowLearnMore(data),
|
mobile: (context) => MobileiGoShowLearnMore(data!),
|
||||||
tablet: DesktopiGoShowLearnMore(data),
|
tablet: (context) => DesktopiGoShowLearnMore(data!),
|
||||||
desktop: DesktopiGoShowLearnMore(data),
|
desktop: (context) => DesktopiGoShowLearnMore(data!),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-11
@@ -14,9 +14,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_login.dart';
|
import '../widgets/mobile/mobile_login.dart';
|
||||||
|
|
||||||
class Login extends StatefulWidget {
|
class Login extends StatefulWidget {
|
||||||
final Key key;
|
const Login({Key? key}) : super(key: key);
|
||||||
|
|
||||||
const Login({this.key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -50,15 +48,15 @@ class LoginState extends State<Login> {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileLogin(),
|
mobile: (context) => MobileLogin(),
|
||||||
tablet: DesktopLogin(),
|
tablet: (context) => DesktopLogin(),
|
||||||
desktop: DesktopLogin(),
|
desktop: (context) => DesktopLogin(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-11
@@ -15,9 +15,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_me.dart';
|
import '../widgets/mobile/mobile_me.dart';
|
||||||
|
|
||||||
class Me extends StatefulWidget {
|
class Me extends StatefulWidget {
|
||||||
final Key key;
|
const Me({Key? key}) : super(key: key);
|
||||||
|
|
||||||
const Me({this.key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -52,16 +50,16 @@ class MeState extends State<Me> {
|
|||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: DoubleBackToCloseAppWrapper(
|
body: DoubleBackToCloseAppWrapper(
|
||||||
child: ScreenTypeLayout(
|
child: ScreenTypeLayout.builder(
|
||||||
mobile: MobileMe(),
|
mobile: (context) => MobileMe(),
|
||||||
tablet: DesktopMe(),
|
tablet: (context) => DesktopMe(),
|
||||||
desktop: DesktopMe(),
|
desktop: (context) => DesktopMe(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 3,),
|
mobile: (context) => MobileBottomNav(currentIndex: 3,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_minipos_learn_more.dart';
|
import '../widgets/mobile/mobile_minipos_learn_more.dart';
|
||||||
|
|
||||||
class MiniPosLearnMore extends StatefulWidget {
|
class MiniPosLearnMore extends StatefulWidget {
|
||||||
const MiniPosLearnMore({Key key}) : super(key: key);
|
const MiniPosLearnMore({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -26,7 +26,7 @@ class MiniPosLearnMore extends StatefulWidget {
|
|||||||
|
|
||||||
class MiniPosLearnMoreState extends State<MiniPosLearnMore> {
|
class MiniPosLearnMoreState extends State<MiniPosLearnMore> {
|
||||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
Map<String, dynamic> data;
|
Map<String, dynamic>? data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -42,35 +42,36 @@ class MiniPosLearnMoreState extends State<MiniPosLearnMore> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WillPopScope(
|
return ResponsiveBuilder(
|
||||||
child: ResponsiveBuilder(
|
builder: (context, sizingInformation) => PopScope(
|
||||||
builder: (context, sizingInformation) =>
|
canPop: true,
|
||||||
Scaffold(
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
key: _scaffoldKey,
|
if (didPop) return;
|
||||||
appBar: MiniNavigationBar(
|
// 可以在这里处理返回逻辑
|
||||||
title: S.of(context).minipos,
|
},
|
||||||
back: true,
|
child: Scaffold(
|
||||||
breadCrumbs: [
|
key: _scaffoldKey,
|
||||||
BreadCrumb(S.of(context).minipos, null),
|
appBar: MiniNavigationBar(
|
||||||
],
|
title: S.of(context).minipos,
|
||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
back: true,
|
||||||
),
|
breadCrumbs: [
|
||||||
drawer: null,
|
BreadCrumb(S.of(context).minipos, null),
|
||||||
body: ScreenTypeLayout(
|
],
|
||||||
mobile: MobileMiniPosLearnMore(data),
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
tablet: DesktopMiniPosLearnMore(data),
|
|
||||||
desktop: DesktopMiniPosLearnMore(data),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
|
||||||
tablet: BottomNav(),
|
|
||||||
desktop: BottomNav(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
drawer: null,
|
||||||
|
body: ScreenTypeLayout.builder(
|
||||||
|
mobile: (context) => MobileMiniPosLearnMore(data!),
|
||||||
|
tablet: (context) => DesktopMiniPosLearnMore(data!),
|
||||||
|
desktop: (context) => DesktopMiniPosLearnMore(data!),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
|
mobile: (context) => MobileBottomNav(currentIndex: 0),
|
||||||
|
tablet: (context) => BottomNav(),
|
||||||
|
desktop: (context) => BottomNav(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onWillPop: () async {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,19 @@ import '../widgets/desktop/desktop_my_addresses.dart';
|
|||||||
import '../widgets/mobile/mobile_my_addresses.dart';
|
import '../widgets/mobile/mobile_my_addresses.dart';
|
||||||
|
|
||||||
class MyAddresses extends StatelessWidget {
|
class MyAddresses extends StatelessWidget {
|
||||||
final Key key;
|
|
||||||
final int businessId;
|
final int businessId;
|
||||||
|
|
||||||
const MyAddresses({this.key, int businessId}) :
|
const MyAddresses({Key? key, int? businessId}) :
|
||||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileMyAddresses(businessId: businessId,),
|
mobile: (context) => MobileMyAddresses(businessId: businessId,),
|
||||||
tablet: DesktopMyAddresses(businessId: businessId,),
|
tablet: (context) => DesktopMyAddresses(businessId: businessId,),
|
||||||
desktop: DesktopMyAddresses(businessId: businessId,),
|
desktop: (context) => DesktopMyAddresses(businessId: businessId,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ class MyCards extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileMyCards(),
|
mobile: (context) => MobileMyCards(),
|
||||||
tablet: DesktopMyCards(),
|
tablet: (context) => DesktopMyCards(),
|
||||||
desktop: DesktopMyCards(),
|
desktop: (context) => DesktopMyCards(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,19 @@ import '../widgets/desktop/desktop_my_support.dart';
|
|||||||
import '../widgets/mobile/mobile_my_support.dart';
|
import '../widgets/mobile/mobile_my_support.dart';
|
||||||
|
|
||||||
class MySupport extends StatelessWidget {
|
class MySupport extends StatelessWidget {
|
||||||
final Key key;
|
|
||||||
final int businessId;
|
final int businessId;
|
||||||
|
|
||||||
const MySupport({this.key, int businessId}) :
|
const MySupport({Key? key, int? businessId}) :
|
||||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileMySupport(businessId: businessId,),
|
mobile: (context) => MobileMySupport(businessId: businessId,),
|
||||||
tablet: DesktopMySupport(businessId: businessId,),
|
tablet: (context) => DesktopMySupport(businessId: businessId,),
|
||||||
desktop: DesktopMySupport(businessId: businessId,),
|
desktop: (context) => DesktopMySupport(businessId: businessId,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,25 +7,24 @@ import '../widgets/desktop/desktop_new_address.dart';
|
|||||||
import '../widgets/mobile/mobile_new_address.dart';
|
import '../widgets/mobile/mobile_new_address.dart';
|
||||||
|
|
||||||
class NewAddress extends StatelessWidget {
|
class NewAddress extends StatelessWidget {
|
||||||
final Key key;
|
final LocatedAddress? locatedAddress;
|
||||||
final LocatedAddress locatedAddress;
|
final int? businessId;
|
||||||
final int businessId;
|
const NewAddress({Key? key, this.locatedAddress, this.businessId}) : super(key: key);
|
||||||
const NewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileNewAddress(
|
mobile: (context) => MobileNewAddress(
|
||||||
locatedAddress: locatedAddress,
|
locatedAddress: locatedAddress,
|
||||||
businessId: businessId,
|
businessId: businessId,
|
||||||
),
|
),
|
||||||
tablet: DesktopNewAddress(
|
tablet: (context) => DesktopNewAddress(
|
||||||
locatedAddress: locatedAddress,
|
locatedAddress: locatedAddress,
|
||||||
businessId: businessId,
|
businessId: businessId,
|
||||||
),
|
),
|
||||||
desktop: DesktopNewAddress(
|
desktop: (context) => DesktopNewAddress(
|
||||||
locatedAddress: locatedAddress,
|
locatedAddress: locatedAddress,
|
||||||
businessId: businessId,
|
businessId: businessId,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import '../widgets/mobile/mobile_new_comment.dart';
|
|||||||
class NewComment extends StatelessWidget {
|
class NewComment extends StatelessWidget {
|
||||||
final int orderId;
|
final int orderId;
|
||||||
|
|
||||||
const NewComment(this.orderId, {Key key}) :
|
const NewComment(this.orderId, {Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ class NewComment extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileNewComment(orderId,),
|
mobile: (context) => MobileNewComment(orderId,),
|
||||||
tablet: DesktopNewComment(orderId,),
|
tablet: (context) => DesktopNewComment(orderId,),
|
||||||
desktop: DesktopNewComment(orderId,),
|
desktop: (context) => DesktopNewComment(orderId,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-11
@@ -14,10 +14,9 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_new_ticket.dart';
|
import '../widgets/mobile/mobile_new_ticket.dart';
|
||||||
|
|
||||||
class NewTicket extends StatefulWidget {
|
class NewTicket extends StatefulWidget {
|
||||||
final Key key;
|
final int? businessId;
|
||||||
final int businessId;
|
|
||||||
|
|
||||||
const NewTicket({this.key, int businessId}) :
|
const NewTicket({Key? key, int? businessId}) :
|
||||||
businessId = businessId ?? Constants.BUSINESS_ID;
|
businessId = businessId ?? Constants.BUSINESS_ID;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -52,15 +51,15 @@ class NewTicketState extends State<NewTicket> {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileNewTicket(businessId: widget.businessId,),
|
mobile: (context) => MobileNewTicket(businessId: widget.businessId!,),
|
||||||
tablet: DesktopNewTicket(businessId: widget.businessId,),
|
tablet: (context) => DesktopNewTicket(businessId: widget.businessId!,),
|
||||||
desktop: DesktopNewTicket(businessId: widget.businessId,),
|
desktop: (context) => DesktopNewTicket(businessId: widget.businessId!,),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_new_user.dart';
|
import '../widgets/mobile/mobile_new_user.dart';
|
||||||
|
|
||||||
class NewUser extends StatelessWidget {
|
class NewUser extends StatelessWidget {
|
||||||
const NewUser({Key key}) : super(key: key);
|
const NewUser({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,15 +28,15 @@ class NewUser extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileNewUser(),
|
mobile: (context) => MobileNewUser(),
|
||||||
tablet: DesktopNewUser(),
|
tablet: (context) => DesktopNewUser(),
|
||||||
desktop: DesktopNewUser(),
|
desktop: (context) => DesktopNewUser(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class OrderDetail extends StatelessWidget {
|
|||||||
final int orderId;
|
final int orderId;
|
||||||
final bool fromOrders;
|
final bool fromOrders;
|
||||||
|
|
||||||
const OrderDetail(this.orderId, {this.fromOrders = false, Key key}) :
|
const OrderDetail(this.orderId, {this.fromOrders = false, Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,10 +20,10 @@ class OrderDetail extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileOrderDetail(orderId, fromOrders: fromOrders,),
|
mobile: (context) => MobileOrderDetail(orderId, fromOrders: fromOrders,),
|
||||||
tablet: DesktopOrderDetail(orderId, fromOrders: fromOrders,),
|
tablet: (context) => DesktopOrderDetail(orderId, fromOrders: fromOrders,),
|
||||||
desktop: DesktopOrderDetail(orderId, fromOrders: fromOrders,),
|
desktop: (context) => DesktopOrderDetail(orderId, fromOrders: fromOrders,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import '../widgets/mobile/mobile_pay_now.dart';
|
|||||||
class PayNow extends StatelessWidget {
|
class PayNow extends StatelessWidget {
|
||||||
final int orderId;
|
final int orderId;
|
||||||
|
|
||||||
const PayNow(this.orderId, {Key key}) :
|
const PayNow(this.orderId, {Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ class PayNow extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobilePayNow(orderId,),
|
mobile: (context) => MobilePayNow(orderId,),
|
||||||
tablet: DesktopPayNow(orderId,),
|
tablet: (context) => DesktopPayNow(orderId,),
|
||||||
desktop: DesktopPayNow(orderId,),
|
desktop: (context) => DesktopPayNow(orderId,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import '../utils/utils.dart';
|
|||||||
|
|
||||||
class PlainPage extends StatefulWidget {
|
class PlainPage extends StatefulWidget {
|
||||||
final int businessId;
|
final int businessId;
|
||||||
final String slug;
|
final String? slug;
|
||||||
|
|
||||||
const PlainPage(this.slug, {this.businessId, Key key}) :
|
const PlainPage(this.slug, {required this.businessId, Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -23,7 +23,7 @@ class PlainPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PlainPageState extends State<PlainPage> {
|
class PlainPageState extends State<PlainPage> {
|
||||||
Blog blog;
|
Blog? blog;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -43,10 +43,10 @@ class PlainPageState extends State<PlainPage> {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobilePlainPage(blog),
|
mobile: (context) => MobilePlainPage(blog!),
|
||||||
tablet: DesktopPlainPage(blog),
|
tablet: (context) => DesktopPlainPage(blog!),
|
||||||
desktop: DesktopPlainPage(blog),
|
desktop: (context) => DesktopPlainPage(blog!),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ProductDetailPage extends StatelessWidget {
|
|||||||
final Business business;
|
final Business business;
|
||||||
final Product product;
|
final Product product;
|
||||||
|
|
||||||
const ProductDetailPage({this.business, this.product, Key key}) :
|
const ProductDetailPage({required this.business, required this.product, Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ class ProductDetailPage extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileProductDetailPage(business: business, product: product,),
|
mobile: (context) => MobileProductDetailPage(business: business, product: product,),
|
||||||
tablet: DesktopProductDetailPage(business: business, product: product,),
|
tablet: (context) => DesktopProductDetailPage(business: business, product: product,),
|
||||||
desktop: DesktopProductDetailPage(business: business, product: product,),
|
desktop: (context) => DesktopProductDetailPage(business: business, product: product,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ import '../widgets/mobile/product_search.dart' as mobile;
|
|||||||
class ProductSearch extends StatelessWidget {
|
class ProductSearch extends StatelessWidget {
|
||||||
final Business business;
|
final Business business;
|
||||||
|
|
||||||
const ProductSearch(this.business, {Key key}) : super(key: key);
|
const ProductSearch(this.business, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: mobile.ProductSearch(business),
|
mobile: (context) => mobile.ProductSearch(business),
|
||||||
tablet: desktop.ProductSearch(business),
|
tablet: (context) => desktop.ProductSearch(business),
|
||||||
desktop: desktop.ProductSearch(business),
|
desktop: (context) => desktop.ProductSearch(business),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import '../widgets/mobile/mobile_renew_license.dart';
|
|||||||
|
|
||||||
class RenewLicense extends StatefulWidget {
|
class RenewLicense extends StatefulWidget {
|
||||||
|
|
||||||
const RenewLicense({Key key}) :
|
const RenewLicense({Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -28,10 +28,10 @@ class RenewLicenseState extends State<RenewLicense> {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileRenewLicense(Constants.BUSINESS_ID),
|
mobile: (context) => MobileRenewLicense(Constants.BUSINESS_ID),
|
||||||
tablet: DesktopRenewLicense(Constants.BUSINESS_ID),
|
tablet: (context) => DesktopRenewLicense(Constants.BUSINESS_ID),
|
||||||
desktop: DesktopRenewLicense(Constants.BUSINESS_ID),
|
desktop: (context) => DesktopRenewLicense(Constants.BUSINESS_ID),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import '../widgets/mobile/mobile_renew_license.dart';
|
|||||||
class RenewMiniOffice extends StatefulWidget {
|
class RenewMiniOffice extends StatefulWidget {
|
||||||
final int gid;
|
final int gid;
|
||||||
|
|
||||||
const RenewMiniOffice(this.gid, {Key key}) :
|
const RenewMiniOffice(this.gid, {Key? key}) :
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -25,7 +25,7 @@ class RenewMiniOffice extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RenewMiniOfficeState extends State<RenewMiniOffice> {
|
class RenewMiniOfficeState extends State<RenewMiniOffice> {
|
||||||
Map<String, dynamic> data;
|
Map<String, dynamic>? data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -45,10 +45,10 @@ class RenewMiniOfficeState extends State<RenewMiniOffice> {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileRenewMiniOffice(data),
|
mobile: (context) => MobileRenewMiniOffice(data!),
|
||||||
tablet: DesktopRenewMiniOffice(data),
|
tablet: (context) => DesktopRenewMiniOffice(data!),
|
||||||
desktop: DesktopRenewMiniOffice(data),
|
desktop: (context) => DesktopRenewMiniOffice(data!),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import '../widgets/mobile/mobile_reset_password.dart';
|
|||||||
class ResetPassword extends StatelessWidget {
|
class ResetPassword extends StatelessWidget {
|
||||||
final String mobile;
|
final String mobile;
|
||||||
final String code;
|
final String code;
|
||||||
const ResetPassword(this.mobile, {Key key, this.code}) : super(key: key);
|
const ResetPassword(this.mobile, {Key? key, required this.code}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -30,15 +30,15 @@ class ResetPassword extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileResetPassword(mobile, code: code,),
|
mobile: (context) => MobileResetPassword(mobile, code: code,),
|
||||||
tablet: DesktopResetPassword(mobile, code: code,),
|
tablet: (context) => DesktopResetPassword(mobile, code: code,),
|
||||||
desktop: DesktopResetPassword(mobile, code: code,),
|
desktop: (context) => DesktopResetPassword(mobile, code: code,),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,18 +5,17 @@ import '../widgets/mobile/mobile_search_place.dart';
|
|||||||
import 'package:responsive_builder/responsive_builder.dart';
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
class SearchPlace extends StatelessWidget {
|
class SearchPlace extends StatelessWidget {
|
||||||
final Key key;
|
|
||||||
final int businessId;
|
final int businessId;
|
||||||
const SearchPlace(this.businessId, {this.key}) : super(key: key);
|
const SearchPlace(this.businessId, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileSearchPlace(businessId),
|
mobile: (context) => MobileSearchPlace(businessId),
|
||||||
tablet: DesktopSearchPlace(businessId),
|
tablet: (context) => DesktopSearchPlace(businessId),
|
||||||
desktop: DesktopSearchPlace(businessId),
|
desktop: (context) => DesktopSearchPlace(businessId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import '../widgets/mobile/mobile_set_password.dart';
|
|||||||
class SetPassword extends StatelessWidget {
|
class SetPassword extends StatelessWidget {
|
||||||
final String mobile;
|
final String mobile;
|
||||||
final String code;
|
final String code;
|
||||||
const SetPassword(this.mobile, {Key key, this.code}) : super(key: key);
|
const SetPassword(this.mobile, {Key? key, required this.code}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -30,15 +30,15 @@ class SetPassword extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileSetPassword(mobile, code: code,),
|
mobile: (context) => MobileSetPassword(mobile, code: code,),
|
||||||
tablet: DesktopSetPassword(mobile, code: code,),
|
tablet: (context) => DesktopSetPassword(mobile, code: code,),
|
||||||
desktop: DesktopSetPassword(mobile, code: code,),
|
desktop: (context) => DesktopSetPassword(mobile, code: code,),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+7
-7
@@ -8,9 +8,9 @@ import '../widgets/desktop/shop.dart' as desktop;
|
|||||||
import '../widgets/mobile/shop.dart' as mobile;
|
import '../widgets/mobile/shop.dart' as mobile;
|
||||||
|
|
||||||
class Shop extends StatefulWidget {
|
class Shop extends StatefulWidget {
|
||||||
final int businessId;
|
final int? businessId;
|
||||||
|
|
||||||
const Shop({this.businessId, Key key}) : super(key: key);
|
const Shop({this.businessId, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => ShopState();
|
State<StatefulWidget> createState() => ShopState();
|
||||||
@@ -20,17 +20,17 @@ class ShopState extends State<Shop> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int _businessId =
|
int _businessId =
|
||||||
widget.businessId == null ? Utils.getBusinessId() : widget.businessId;
|
widget.businessId == null ? Utils.getBusinessId() : widget.businessId!;
|
||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) => ScreenTypeLayout(
|
builder: (context, sizingInformation) => ScreenTypeLayout.builder(
|
||||||
mobile: mobile.Shop(
|
mobile: (context) => mobile.Shop(
|
||||||
businessId: _businessId,
|
businessId: _businessId,
|
||||||
),
|
),
|
||||||
tablet: desktop.Shop(
|
tablet: (context) => desktop.Shop(
|
||||||
businessId: _businessId,
|
businessId: _businessId,
|
||||||
),
|
),
|
||||||
desktop: desktop.Shop(
|
desktop: (context) => desktop.Shop(
|
||||||
businessId: _businessId,
|
businessId: _businessId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import '../widgets/mobile/mobile_store_product_search.dart';
|
|||||||
class StoreProductSearch extends StatelessWidget {
|
class StoreProductSearch extends StatelessWidget {
|
||||||
final Business business;
|
final Business business;
|
||||||
|
|
||||||
const StoreProductSearch(this.business, {Key key}) : super(key: key);
|
const StoreProductSearch(this.business, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -31,15 +31,15 @@ class StoreProductSearch extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileStoreProductSearch(business,),
|
mobile: (context) => MobileStoreProductSearch(business,),
|
||||||
tablet: DesktopStoreProductSearch(business),
|
tablet: (context) => DesktopStoreProductSearch(business),
|
||||||
desktop: DesktopStoreProductSearch(business),
|
desktop: (context) => DesktopStoreProductSearch(business),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 1,),
|
mobile: (context) => MobileBottomNav(currentIndex: 1,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import '../widgets/mobile/mobile_stripe_pay_web.dart';
|
|||||||
class StripePayWeb extends StatelessWidget {
|
class StripePayWeb extends StatelessWidget {
|
||||||
final Order order;
|
final Order order;
|
||||||
final PaymentPlatform paymentPlatform;
|
final PaymentPlatform paymentPlatform;
|
||||||
final StripePaymentMethod stripePaymentMethod;
|
final StripePaymentMethod? stripePaymentMethod;
|
||||||
|
|
||||||
const StripePayWeb(this.order, this.paymentPlatform, {this.stripePaymentMethod});
|
const StripePayWeb(this.order, this.paymentPlatform, {this.stripePaymentMethod});
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ class StripePayWeb extends StatelessWidget {
|
|||||||
|
|
||||||
return ResponsiveBuilder(
|
return ResponsiveBuilder(
|
||||||
builder: (context, sizingInformation) =>
|
builder: (context, sizingInformation) =>
|
||||||
ScreenTypeLayout(
|
ScreenTypeLayout.builder(
|
||||||
mobile: MobileStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
|
mobile: (context) => MobileStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
|
||||||
tablet: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
|
tablet: (context) => DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
|
||||||
desktop: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
|
desktop: (context) => DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_user_profile.dart';
|
import '../widgets/mobile/mobile_user_profile.dart';
|
||||||
|
|
||||||
class UserProfile extends StatelessWidget {
|
class UserProfile extends StatelessWidget {
|
||||||
const UserProfile({Key key}) : super(key: key);
|
const UserProfile({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -29,15 +29,15 @@ class UserProfile extends StatelessWidget {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileUserProfile(),
|
mobile: (context) => MobileUserProfile(),
|
||||||
tablet: DesktopUserProfile(),
|
tablet: (context) => DesktopUserProfile(),
|
||||||
desktop: DesktopUserProfile(),
|
desktop: (context) => DesktopUserProfile(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_view_blog.dart';
|
import '../widgets/mobile/mobile_view_blog.dart';
|
||||||
|
|
||||||
class ViewBlog extends StatefulWidget {
|
class ViewBlog extends StatefulWidget {
|
||||||
final Key key;
|
|
||||||
final int bid;
|
final int bid;
|
||||||
|
|
||||||
const ViewBlog(this.bid, {this.key}) : super(key: key);
|
const ViewBlog(this.bid, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -51,15 +50,15 @@ class ViewBlogState extends State<ViewBlog> {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileViewBlog(widget.bid),
|
mobile: (context) => MobileViewBlog(widget.bid),
|
||||||
tablet: DesktopViewBlog(widget.bid),
|
tablet: (context) => DesktopViewBlog(widget.bid),
|
||||||
desktop: DesktopViewBlog(widget.bid),
|
desktop: (context) => DesktopViewBlog(widget.bid),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import '../widgets/mobile/MobileBottomNav.dart';
|
|||||||
import '../widgets/mobile/mobile_view_ticket.dart';
|
import '../widgets/mobile/mobile_view_ticket.dart';
|
||||||
|
|
||||||
class ViewTicket extends StatefulWidget {
|
class ViewTicket extends StatefulWidget {
|
||||||
final Key key;
|
|
||||||
final int ticketId;
|
final int ticketId;
|
||||||
|
|
||||||
const ViewTicket(this.ticketId, {this.key}) : super(key: key);
|
const ViewTicket(this.ticketId, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -51,15 +50,15 @@ class ViewTicketState extends State<ViewTicket> {
|
|||||||
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
|
||||||
),
|
),
|
||||||
drawer: null,
|
drawer: null,
|
||||||
body: ScreenTypeLayout(
|
body: ScreenTypeLayout.builder(
|
||||||
mobile: MobileViewTicket(widget.ticketId),
|
mobile: (context) => MobileViewTicket(widget.ticketId),
|
||||||
tablet: DesktopViewTicket(widget.ticketId),
|
tablet: (context) => DesktopViewTicket(widget.ticketId),
|
||||||
desktop: DesktopViewTicket(widget.ticketId),
|
desktop: (context) => DesktopViewTicket(widget.ticketId),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: ScreenTypeLayout(
|
bottomNavigationBar: ScreenTypeLayout.builder(
|
||||||
mobile: MobileBottomNav(currentIndex: 0,),
|
mobile: (context) => MobileBottomNav(currentIndex: 0,),
|
||||||
tablet: BottomNav(),
|
tablet: (context) => BottomNav(),
|
||||||
desktop: BottomNav(),
|
desktop: (context) => BottomNav(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+58
-58
@@ -44,169 +44,169 @@ class Routes {
|
|||||||
|
|
||||||
static void configure() {
|
static void configure() {
|
||||||
router.define('/', handler: new Handler(
|
router.define('/', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Home(title: Constants.APP_TITLE,);
|
return Home(title: Constants.APP_TITLE,);
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.fadeIn
|
transitionType: TransitionType.fadeIn
|
||||||
);
|
);
|
||||||
router.define('/download', handler: new Handler(
|
router.define('/download', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Download();
|
return Download();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/minipos-learn-more', handler: new Handler(
|
router.define('/minipos-learn-more', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return MiniPosLearnMore();
|
return MiniPosLearnMore();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/igoshow-learn-more', handler: new Handler(
|
router.define('/igoshow-learn-more', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return IGoShowLearnMore();
|
return IGoShowLearnMore();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/login', handler: new Handler(
|
router.define('/login', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Login();
|
return Login();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/me', handler: new Handler(
|
router.define('/me', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Me();
|
return Me();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/change-password', handler: new Handler(
|
router.define('/change-password', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return ChangePassword();
|
return ChangePassword();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/user-profile', handler: new Handler(
|
router.define('/user-profile', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return UserProfile();
|
return UserProfile();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/new-user', handler: new Handler(
|
router.define('/new-user', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return NewUser();
|
return NewUser();
|
||||||
}),
|
}),
|
||||||
transitionType: TransitionType.inFromRight
|
transitionType: TransitionType.inFromRight
|
||||||
);
|
);
|
||||||
router.define('/set-password/:mobile/:code', handler: new Handler(
|
router.define('/set-password/:mobile/:code', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return SetPassword(params['mobile'][0], code: params['code'][0]);
|
return SetPassword(params['mobile']![0], code: params['code']![0]);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/forgot-password', handler: new Handler(
|
router.define('/forgot-password', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return ForgotPassword();
|
return ForgotPassword();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/reset-password/:mobile/:code', handler: new Handler(
|
router.define('/reset-password/:mobile/:code', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return ResetPassword(params['mobile'][0], code: params['code'][0]);
|
return ResetPassword(params['mobile']![0], code: params['code']![0]);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/change-mobile-email/:ismobile', handler: new Handler(
|
router.define('/change-mobile-email/:ismobile', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
if (params['ismobile'][0] == '1') {
|
if (params['ismobile']![0] == '1') {
|
||||||
return ChangeMobileOrEmail(true);
|
return ChangeMobileOrEmail(true);
|
||||||
}
|
}
|
||||||
return ChangeMobileOrEmail(false);
|
return ChangeMobileOrEmail(false);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/my-addresses/:business_id', handler: new Handler(
|
router.define('/my-addresses/:business_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return MyAddresses(businessId: int.parse(params['business_id'][0]),);
|
return MyAddresses(businessId: int.parse(params['business_id']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/my-support/:business_id', handler: new Handler(
|
router.define('/my-support/:business_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return MySupport(businessId: int.parse(params['business_id'][0]),);
|
return MySupport(businessId: int.parse(params['business_id']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/new-ticket/:business_id', handler: new Handler(
|
router.define('/new-ticket/:business_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return NewTicket(businessId: int.parse(params['business_id'][0]),);
|
return NewTicket(businessId: int.parse(params['business_id']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/search-place/:business_id', handler: new Handler(
|
router.define('/search-place/:business_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return SearchPlace(int.parse(params['business_id'][0]));
|
return SearchPlace(int.parse(params['business_id']![0]));
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/view-ticket/:ticket_id', handler: new Handler(
|
router.define('/view-ticket/:ticket_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return ViewTicket(int.parse(params['ticket_id'][0]),);
|
return ViewTicket(int.parse(params['ticket_id']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/blog/:business_id', handler: new Handler(
|
router.define('/blog/:business_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Blog(businessId: int.parse(params['business_id'][0]),);
|
return Blog(businessId: int.parse(params['business_id']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/view-blog/:bid', handler: new Handler(
|
router.define('/view-blog/:bid', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return ViewBlog(int.parse(params['bid'][0]),);
|
return ViewBlog(int.parse(params['bid']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/shop/:business_id', handler: new Handler(
|
router.define('/shop/:business_id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Shop(businessId: int.parse(params['business_id'][0]),);
|
return Shop(businessId: int.parse(params['business_id']![0]),);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/shop', handler: new Handler(
|
router.define('/shop', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Shop();
|
return Shop();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
// router.define('/ocr-scan', handler: new Handler(
|
// router.define('/ocr-scan', handler: new Handler(
|
||||||
// handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
// handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
// return OCRScan();
|
// return OCRScan();
|
||||||
// }
|
// }
|
||||||
// ));
|
// ));
|
||||||
router.define('/checkout/:id', handler: new Handler(
|
router.define('/checkout/:id', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Checkout(int.parse(params['id'][0]));
|
return Checkout(int.parse(params['id']![0]));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/paynow/:orderId', handler: new Handler(
|
router.define('/paynow/:orderId', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return PayNow(int.parse(params['orderId'][0]));
|
return PayNow(int.parse(params['orderId']![0]));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/orders', handler: new Handler(
|
router.define('/orders', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Orders();
|
return Orders();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/my-cards', handler: new Handler(
|
router.define('/my-cards', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return MyCards();
|
return MyCards();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/orderdetail/:orderId', handler: new Handler(
|
router.define('/orderdetail/:orderId', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return OrderDetail(int.parse(params['orderId'][0]));
|
return OrderDetail(int.parse(params['orderId']![0]));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/new-comment/:orderId', handler: new Handler(
|
router.define('/new-comment/:orderId', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return NewComment(int.parse(params['orderId'][0]));
|
return NewComment(int.parse(params['orderId']![0]));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/coupons/:contactId', handler: new Handler(
|
router.define('/coupons/:contactId', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return Coupons(int.parse(params['contactId'][0]));
|
return Coupons(int.parse(params['contactId']![0]));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/service-policy', handler: new Handler(
|
router.define('/service-policy', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return PlainPage(
|
return PlainPage(
|
||||||
'service-policy',
|
'service-policy',
|
||||||
// businessId: Constants.BUSINESS_ID,
|
// businessId: Constants.BUSINESS_ID,
|
||||||
@@ -215,7 +215,7 @@ class Routes {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/return-policy', handler: new Handler(
|
router.define('/return-policy', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return PlainPage(
|
return PlainPage(
|
||||||
'return-policy',
|
'return-policy',
|
||||||
// businessId: Constants.BUSINESS_ID,
|
// businessId: Constants.BUSINESS_ID,
|
||||||
@@ -224,7 +224,7 @@ class Routes {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/privacy-policy', handler: new Handler(
|
router.define('/privacy-policy', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return PlainPage(
|
return PlainPage(
|
||||||
'privacy-policy',
|
'privacy-policy',
|
||||||
// businessId: Constants.BUSINESS_ID,
|
// businessId: Constants.BUSINESS_ID,
|
||||||
@@ -233,7 +233,7 @@ class Routes {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/end-user-license-agreement', handler: new Handler(
|
router.define('/end-user-license-agreement', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return PlainPage(
|
return PlainPage(
|
||||||
'end-user-license-agreement',
|
'end-user-license-agreement',
|
||||||
// businessId: Constants.BUSINESS_ID,
|
// businessId: Constants.BUSINESS_ID,
|
||||||
@@ -242,7 +242,7 @@ class Routes {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/about-us', handler: new Handler(
|
router.define('/about-us', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return PlainPage(
|
return PlainPage(
|
||||||
'about-us',
|
'about-us',
|
||||||
// businessId: Constants.BUSINESS_ID,
|
// businessId: Constants.BUSINESS_ID,
|
||||||
@@ -251,29 +251,29 @@ class Routes {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/contact-us', handler: new Handler(
|
router.define('/contact-us', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return ContactUs(
|
return ContactUs(
|
||||||
businessId: Constants.BUSINESS_ID,
|
businessId: Constants.BUSINESS_ID,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
router.define('/renew-license', handler: new Handler(
|
router.define('/renew-license', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return RenewLicense();
|
return RenewLicense();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/renew-minioffice/:gid', handler: new Handler(
|
router.define('/renew-minioffice/:gid', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return RenewMiniOffice(int.parse(params['gid'][0]));
|
return RenewMiniOffice(int.parse(params['gid']![0]));
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/buy-service/:gid/:servicename', handler: new Handler(
|
router.define('/buy-service/:gid/:servicename', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return BuyService(int.parse(params['gid'][0]), params['servicename'][0]);
|
return BuyService(int.parse(params['gid']![0]), params['servicename']![0]);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
router.define('/contact-stores', handler: new Handler(
|
router.define('/contact-stores', handler: new Handler(
|
||||||
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
|
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
|
||||||
return CreateOnlineStore1();
|
return CreateOnlineStore1();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ class UpdateContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UpdateLocale {
|
class UpdateLocale {
|
||||||
final Locale locale;
|
final Locale? locale;
|
||||||
UpdateLocale(this.locale);
|
UpdateLocale(this.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateCurrentUser {
|
class UpdateCurrentUser {
|
||||||
final User user;
|
final User? user;
|
||||||
UpdateCurrentUser(this.user);
|
UpdateCurrentUser(this.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateRedirectRoute {
|
class UpdateRedirectRoute {
|
||||||
final String route;
|
final String? route;
|
||||||
UpdateRedirectRoute(this.route);
|
UpdateRedirectRoute(this.route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import 'package:redux/redux.dart';
|
|||||||
|
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final cartInfoReducer = combineReducers<List<CartInfo>>([
|
final cartInfoReducer = combineReducers<List<CartInfo>?>([
|
||||||
TypedReducer<List<CartInfo>, UpdateCartInfo>(_updateCartInfo)
|
TypedReducer<List<CartInfo>?, UpdateCartInfo>(_updateCartInfo)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
List<CartInfo> _updateCartInfo(List<CartInfo> cartInfos, action) {
|
List<CartInfo> _updateCartInfo(List<CartInfo>? cartInfos, action) {
|
||||||
return action.cartInfos;
|
return action.cartInfos;
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_wisetronic/store/actions.dart';
|
import 'package:flutter_wisetronic/store/actions.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
|
|
||||||
final contextReducer = combineReducers<BuildContext>([
|
final contextReducer = combineReducers<BuildContext?>([
|
||||||
TypedReducer<BuildContext, UpdateContext>(_updateContext)
|
TypedReducer<BuildContext?, UpdateContext>(_updateContext)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
BuildContext _updateContext(BuildContext context, action) {
|
BuildContext _updateContext(BuildContext? context, action) {
|
||||||
return action.context;
|
return action.context;
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
|
|||||||
|
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final deviceIdReducer = combineReducers<String>([
|
final deviceIdReducer = combineReducers<String?>([
|
||||||
TypedReducer<String, UpdateDeviceId>(_updateDeviceId)
|
TypedReducer<String?, UpdateDeviceId>(_updateDeviceId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
String _updateDeviceId(String deviceId, action) {
|
String _updateDeviceId(String? deviceId, action) {
|
||||||
return action.deviceId;
|
return action.deviceId;
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
|
|||||||
|
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final fcmtokenReducer = combineReducers<String>([
|
final fcmtokenReducer = combineReducers<String?>([
|
||||||
TypedReducer<String, UpdateFcmToken>(_updateFcmToken)
|
TypedReducer<String?, UpdateFcmToken>(_updateFcmToken)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
String _updateFcmToken(String token, action) {
|
String _updateFcmToken(String? token, action) {
|
||||||
return action.token;
|
return action.token;
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
|
|||||||
|
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final lastVisitReducer = combineReducers<List<int>>([
|
final lastVisitReducer = combineReducers<List<int>?>([
|
||||||
TypedReducer<List<int>, UpdateLastVisit>(_updateLastVisit)
|
TypedReducer<List<int>?, UpdateLastVisit>(_updateLastVisit)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
List<int> _updateLastVisit(List<int> lastVisit, action) {
|
List<int> _updateLastVisit(List<int>? lastVisit, action) {
|
||||||
return action.lastVisit;
|
return action.lastVisit;
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_wisetronic/store/actions.dart';
|
import 'package:flutter_wisetronic/store/actions.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
|
|
||||||
final localeReducer = combineReducers<Locale>([
|
final localeReducer = combineReducers<Locale?>([
|
||||||
TypedReducer<Locale, UpdateLocale>(_updateLocale)
|
TypedReducer<Locale?, UpdateLocale>(_updateLocale)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Locale _updateLocale(Locale locale, action) {
|
Locale _updateLocale(Locale? locale, action) {
|
||||||
return action.locale;
|
return action.locale;
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,10 @@ import 'package:redux/redux.dart';
|
|||||||
import '../../models/located_address.dart';
|
import '../../models/located_address.dart';
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final locatedAddressReducer = combineReducers<LocatedAddress>([
|
final locatedAddressReducer = combineReducers<LocatedAddress?>([
|
||||||
TypedReducer<LocatedAddress, UpdateLocatedAddress>(_updateLocatedAddress)
|
TypedReducer<LocatedAddress?, UpdateLocatedAddress>(_updateLocatedAddress)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
LocatedAddress _updateLocatedAddress(LocatedAddress locatedAddress, action) {
|
LocatedAddress _updateLocatedAddress(LocatedAddress? locatedAddress, action) {
|
||||||
return action.locatedAddress;
|
return action.locatedAddress;
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final redirectRouteReducer = combineReducers<String>([
|
final redirectRouteReducer = combineReducers<String?>([
|
||||||
TypedReducer<String, UpdateRedirectRoute>(_updateRedirectRoute)
|
TypedReducer<String?, UpdateRedirectRoute>(_updateRedirectRoute)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
String _updateRedirectRoute(String route, action) {
|
String? _updateRedirectRoute(String? route, action) {
|
||||||
return action.route;
|
return action.route;
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
|
|||||||
|
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final tableNumberReducer = combineReducers<String>([
|
final tableNumberReducer = combineReducers<String?>([
|
||||||
TypedReducer<String, UpdateTableNumber>(_updateTableNumber)
|
TypedReducer<String?, UpdateTableNumber>(_updateTableNumber)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
String _updateTableNumber(String tableNumber, action) {
|
String _updateTableNumber(String? tableNumber, action) {
|
||||||
return action.tableNumber;
|
return action.tableNumber;
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,10 @@ import 'package:flutter_wisetronic/models/user.dart';
|
|||||||
|
|
||||||
import '../actions.dart';
|
import '../actions.dart';
|
||||||
|
|
||||||
final userReducer = combineReducers<User>([
|
final userReducer = combineReducers<User?>([
|
||||||
TypedReducer<User, UpdateCurrentUser>(_updateCurrentUser)
|
TypedReducer<User?, UpdateCurrentUser>(_updateCurrentUser)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
User _updateCurrentUser(User user, action) {
|
User? _updateCurrentUser(User? user, action) {
|
||||||
return action.user;
|
return action.user;
|
||||||
}
|
}
|
||||||
@@ -6,16 +6,16 @@ import '../../models/user.dart';
|
|||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class AppState {
|
class AppState {
|
||||||
final BuildContext context;
|
final BuildContext? context;
|
||||||
final Locale locale;
|
final Locale? locale;
|
||||||
final User user;
|
final User? user;
|
||||||
final String redirectRoute;
|
final String? redirectRoute;
|
||||||
final List<CartInfo> cartInfos;
|
final List<CartInfo>? cartInfos;
|
||||||
final LocatedAddress locatedAddress;
|
final LocatedAddress? locatedAddress;
|
||||||
final String fcmToken;
|
final String? fcmToken;
|
||||||
final List<int> lastVisit;
|
final List<int>? lastVisit;
|
||||||
final String deviceId;
|
final String? deviceId;
|
||||||
final String tableNumber;
|
final String? tableNumber;
|
||||||
|
|
||||||
AppState({this.context, this.locale, this.user, this.redirectRoute,
|
AppState({this.context, this.locale, this.user, this.redirectRoute,
|
||||||
this.cartInfos, this.locatedAddress, this.fcmToken, this.lastVisit,
|
this.cartInfos, this.locatedAddress, this.fcmToken, this.lastVisit,
|
||||||
@@ -24,8 +24,8 @@ class AppState {
|
|||||||
factory AppState.init() => AppState();
|
factory AppState.init() => AppState();
|
||||||
|
|
||||||
AppState copyWith({
|
AppState copyWith({
|
||||||
BuildContext context,
|
BuildContext? context,
|
||||||
Locale locale}) {
|
Locale? locale}) {
|
||||||
return AppState(
|
return AppState(
|
||||||
context: context ?? this.context,
|
context: context ?? this.context,
|
||||||
locale: locale ?? this.locale,
|
locale: locale ?? this.locale,
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ class DoubleBackToCloseApp extends StatefulWidget {
|
|||||||
/// Creates a widget that allows the user to close the app by double tapping
|
/// Creates a widget that allows the user to close the app by double tapping
|
||||||
/// the back-button.
|
/// the back-button.
|
||||||
const DoubleBackToCloseApp({
|
const DoubleBackToCloseApp({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.snackBar,
|
required this.snackBar,
|
||||||
@required this.child,
|
required this.child,
|
||||||
}) : assert(snackBar != null),
|
}) : assert(snackBar != null),
|
||||||
assert(child != null),
|
assert(child != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
@@ -34,7 +34,7 @@ class DoubleBackToCloseApp extends StatefulWidget {
|
|||||||
|
|
||||||
class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
|
class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
|
||||||
/// The last time the user tapped Android's back-button.
|
/// The last time the user tapped Android's back-button.
|
||||||
DateTime _lastTimeBackButtonWasTapped;
|
DateTime? _lastTimeBackButtonWasTapped;
|
||||||
|
|
||||||
/// Returns whether the current platform is Android.
|
/// Returns whether the current platform is Android.
|
||||||
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
|
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
|
||||||
@@ -50,7 +50,7 @@ class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
|
|||||||
bool get _isSnackBarVisible =>
|
bool get _isSnackBarVisible =>
|
||||||
(_lastTimeBackButtonWasTapped != null) &&
|
(_lastTimeBackButtonWasTapped != null) &&
|
||||||
(widget.snackBar.duration >
|
(widget.snackBar.duration >
|
||||||
DateTime.now().difference(_lastTimeBackButtonWasTapped));
|
DateTime.now().difference(_lastTimeBackButtonWasTapped!));
|
||||||
|
|
||||||
/// Returns whether the next back navigation of this route will be handled
|
/// Returns whether the next back navigation of this route will be handled
|
||||||
/// internally.
|
/// internally.
|
||||||
@@ -59,7 +59,7 @@ class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
|
|||||||
/// local-history of the current route, in order to handle pop. This is done
|
/// local-history of the current route, in order to handle pop. This is done
|
||||||
/// by [Drawer], for example, so it can close on pop.
|
/// by [Drawer], for example, so it can close on pop.
|
||||||
bool get _willHandlePopInternally =>
|
bool get _willHandlePopInternally =>
|
||||||
ModalRoute.of(context).willHandlePopInternally;
|
ModalRoute.of(context)!.willHandlePopInternally;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class IFrameWeb extends StatefulWidget {
|
class IFrameWeb extends StatefulWidget {
|
||||||
final String width;
|
final String? width;
|
||||||
final String height;
|
final String? height;
|
||||||
final String src;
|
final String? src;
|
||||||
|
|
||||||
const IFrameWeb({this.width, this.height, this.src});
|
const IFrameWeb({this.width, this.height, this.src});
|
||||||
|
|
||||||
|
|||||||
+59
-60
@@ -7,14 +7,12 @@ import 'package:crypto/crypto.dart';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import '../store/store.dart';
|
import '../store/store.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:universal_io/io.dart';
|
|
||||||
|
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
|
|
||||||
typedef void PostCallback(Response response);
|
typedef void PostCallback(Response response);
|
||||||
|
|
||||||
String generateSignature(String string, {String key}) {
|
String generateSignature(String string, {String? key}) {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
key = Constants.API_SECRET;
|
key = Constants.API_SECRET;
|
||||||
}
|
}
|
||||||
@@ -37,17 +35,17 @@ class HttpUtil {
|
|||||||
'Http-Contact-Authorization': '',
|
'Http-Contact-Authorization': '',
|
||||||
'Http-Device-Type': Utils.getOs(checkWeb: true),
|
'Http-Device-Type': Utils.getOs(checkWeb: true),
|
||||||
'Http-Api-Branch': 'flutter',
|
'Http-Api-Branch': 'flutter',
|
||||||
'Http-Language-Code': store.state.locale.languageCode,
|
'Http-Language-Code': (store == null || store.state == null || store.state.locale == null) ? 'en' : store.state.locale!.languageCode,
|
||||||
'Http-Country-Code': store.state.locale.countryCode,
|
'Http-Country-Code': (store == null || store.state == null || store.state.locale == null) ? 'US' : store.state.locale!.countryCode!,
|
||||||
};
|
};
|
||||||
|
|
||||||
static Future<dynamic> httpGet(String url,
|
static Future<dynamic> httpGet(String url,
|
||||||
{
|
{
|
||||||
Map<String, dynamic> queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
int businessId = 0,
|
int businessId = 0,
|
||||||
Function(int, int) receiveProgress,
|
Function(int, int)? receiveProgress,
|
||||||
bool returnError = false,
|
bool returnError = false,
|
||||||
Map<String, String> additionalHeaders,
|
Map<String, String>? additionalHeaders,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, dynamic> localHeaders = json.decode(json.encode(headers));
|
Map<String, dynamic> localHeaders = json.decode(json.encode(headers));
|
||||||
if (additionalHeaders != null) {
|
if (additionalHeaders != null) {
|
||||||
@@ -59,8 +57,7 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
||||||
Box box = await Utils.getBox();
|
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
|
||||||
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
|
||||||
localHeaders['Http-App-Key'] = platformName;
|
localHeaders['Http-App-Key'] = platformName;
|
||||||
localHeaders['Http-Device-Type'] = platformName;
|
localHeaders['Http-Device-Type'] = platformName;
|
||||||
String requestUrl = Constants.BASE_API_URL + url;
|
String requestUrl = Constants.BASE_API_URL + url;
|
||||||
@@ -69,7 +66,9 @@ class HttpUtil {
|
|||||||
requestUrl = url;
|
requestUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.jsonPrettyPrint(queryParameters);
|
if (queryParameters != null) {
|
||||||
|
Utils.jsonPrettyPrint(queryParameters!);
|
||||||
|
}
|
||||||
|
|
||||||
Dio dio = Dio();
|
Dio dio = Dio();
|
||||||
try {
|
try {
|
||||||
@@ -84,9 +83,9 @@ class HttpUtil {
|
|||||||
// print('response data: ${response.data}');
|
// print('response data: ${response.data}');
|
||||||
// Utils.jsonPrettyPrint(response.data);
|
// Utils.jsonPrettyPrint(response.data);
|
||||||
|
|
||||||
int statusCode = response.statusCode;
|
int statusCode = response.statusCode!;
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch(e) {
|
} on DioException catch(e) {
|
||||||
print('HttpGet failed, request: $requestUrl, headers: $localHeaders, error ${e.response}');
|
print('HttpGet failed, request: $requestUrl, headers: $localHeaders, error ${e.response}');
|
||||||
if (returnError) {
|
if (returnError) {
|
||||||
return e;
|
return e;
|
||||||
@@ -95,16 +94,16 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<dynamic> httpPost(String url, PostCallback callback,
|
static Future<dynamic> httpPost(String url, PostCallback? callback,
|
||||||
{
|
{
|
||||||
Map<String, dynamic> queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
int businessId = 0,
|
int businessId = 0,
|
||||||
bool isFormData = false,
|
bool isFormData = false,
|
||||||
Map<String, String> additionalHeaders,
|
Map<String, String>? additionalHeaders,
|
||||||
Map<String, dynamic> body,
|
Map<String, dynamic>? body,
|
||||||
FormData formData,
|
FormData? formData,
|
||||||
Function(int, int) sendProgress,
|
Function(int, int)? sendProgress,
|
||||||
Function(int, int) receiveProgress,
|
Function(int, int)? receiveProgress,
|
||||||
bool returnError = false,
|
bool returnError = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
@@ -116,8 +115,7 @@ class HttpUtil {
|
|||||||
localHeaders['Http-Signature'] = generateSignature(url);
|
localHeaders['Http-Signature'] = generateSignature(url);
|
||||||
}
|
}
|
||||||
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
||||||
Box box = await Utils.getBox();
|
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
|
||||||
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
|
||||||
localHeaders['Http-App-Key'] = platformName;
|
localHeaders['Http-App-Key'] = platformName;
|
||||||
localHeaders['Http-Device-Type'] = platformName;
|
localHeaders['Http-Device-Type'] = platformName;
|
||||||
|
|
||||||
@@ -138,7 +136,7 @@ class HttpUtil {
|
|||||||
Response response = await dio.post(
|
Response response = await dio.post(
|
||||||
requestUrl,
|
requestUrl,
|
||||||
queryParameters: queryParameters == null ? {} : queryParameters,
|
queryParameters: queryParameters == null ? {} : queryParameters,
|
||||||
data: isFormData ? ((formData != null)?formData:FormData.fromMap(body)) : json.encode(body),
|
data: isFormData ? ((formData != null)?formData:FormData.fromMap(body!)) : json.encode(body),
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: localHeaders,
|
headers: localHeaders,
|
||||||
contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType,
|
contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType,
|
||||||
@@ -149,17 +147,17 @@ class HttpUtil {
|
|||||||
|
|
||||||
// Utils.jsonPrettyPrint(response.data);
|
// Utils.jsonPrettyPrint(response.data);
|
||||||
|
|
||||||
int statusCode = response.statusCode;
|
int statusCode = response.statusCode!;
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback(response);
|
callback(response);
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch(e, stacktrace) {
|
} on DioException catch(e, stacktrace) {
|
||||||
if (e.response != null) {
|
if (e.response != null) {
|
||||||
try {
|
try {
|
||||||
Utils.jsonPrettyPrint(e.response.data);
|
Utils.jsonPrettyPrint(e.response?.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
print(e.response.data);
|
print(e.response?.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (returnError) {
|
if (returnError) {
|
||||||
@@ -169,14 +167,14 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<dynamic> httpPut(String url, PostCallback callback,
|
static Future<dynamic> httpPut(String url, PostCallback? callback,
|
||||||
{
|
{
|
||||||
Map<String, dynamic> queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
int businessId = 0,
|
int businessId = 0,
|
||||||
Map<String, String> additionalHeaders,
|
Map<String, String>? additionalHeaders,
|
||||||
Map<String, dynamic> body,
|
Map<String, dynamic>? body,
|
||||||
Function(int, int) sendProgress,
|
Function(int, int)? sendProgress,
|
||||||
Function(int, int) receiveProgress,
|
Function(int, int)? receiveProgress,
|
||||||
bool returnError = false,
|
bool returnError = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
@@ -190,8 +188,7 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
||||||
Box box = await Utils.getBox();
|
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
|
||||||
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
|
||||||
localHeaders['Http-App-Key'] = platformName;
|
localHeaders['Http-App-Key'] = platformName;
|
||||||
localHeaders['Http-Device-Type'] = platformName;
|
localHeaders['Http-Device-Type'] = platformName;
|
||||||
|
|
||||||
@@ -218,14 +215,14 @@ class HttpUtil {
|
|||||||
|
|
||||||
// Utils.jsonPrettyPrint(response.data);
|
// Utils.jsonPrettyPrint(response.data);
|
||||||
|
|
||||||
int statusCode = response.statusCode;
|
int statusCode = response.statusCode!;
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback(response);
|
callback(response);
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch(e) {
|
} on DioException catch(e) {
|
||||||
if (e.response != null) {
|
if (e.response != null) {
|
||||||
Utils.jsonPrettyPrint(e.response.data);
|
Utils.jsonPrettyPrint(e.response!.data);
|
||||||
}
|
}
|
||||||
if (returnError) {
|
if (returnError) {
|
||||||
return e;
|
return e;
|
||||||
@@ -234,14 +231,14 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<dynamic> httpPatch(String url, PostCallback callback,
|
static Future<dynamic> httpPatch(String url, PostCallback? callback,
|
||||||
{
|
{
|
||||||
Map<String, dynamic> queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
int businessId = 0,
|
int businessId = 0,
|
||||||
Map<String, String> additionalHeaders,
|
Map<String, String>? additionalHeaders,
|
||||||
Map<String, dynamic> body,
|
Map<String, dynamic>? body,
|
||||||
Function(int, int) sendProgress,
|
Function(int, int)? sendProgress,
|
||||||
Function(int, int) receiveProgress,
|
Function(int, int)? receiveProgress,
|
||||||
bool returnError = false,
|
bool returnError = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
@@ -255,8 +252,7 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
||||||
Box box = await Utils.getBox();
|
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
|
||||||
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
|
||||||
localHeaders['Http-App-Key'] = platformName;
|
localHeaders['Http-App-Key'] = platformName;
|
||||||
localHeaders['Http-Device-Type'] = platformName;
|
localHeaders['Http-Device-Type'] = platformName;
|
||||||
|
|
||||||
@@ -268,7 +264,9 @@ class HttpUtil {
|
|||||||
requestUrl = url;
|
requestUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.jsonPrettyPrint(body);
|
if (body != null) {
|
||||||
|
Utils.jsonPrettyPrint(body!);
|
||||||
|
}
|
||||||
|
|
||||||
Dio dio = Dio();
|
Dio dio = Dio();
|
||||||
try {
|
try {
|
||||||
@@ -285,14 +283,14 @@ class HttpUtil {
|
|||||||
|
|
||||||
// Utils.jsonPrettyPrint(response.data);
|
// Utils.jsonPrettyPrint(response.data);
|
||||||
|
|
||||||
int statusCode = response.statusCode;
|
int statusCode = response.statusCode!;
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback(response);
|
callback(response);
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch(e) {
|
} on DioException catch(e) {
|
||||||
if (e.response != null) {
|
if (e.response != null) {
|
||||||
Utils.jsonPrettyPrint(e.response.data);
|
Utils.jsonPrettyPrint(e.response!.data);
|
||||||
}
|
}
|
||||||
if (returnError) {
|
if (returnError) {
|
||||||
return e;
|
return e;
|
||||||
@@ -301,12 +299,12 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<dynamic> httpDelete(String url, PostCallback callback,
|
static Future<dynamic> httpDelete(String url, PostCallback? callback,
|
||||||
{
|
{
|
||||||
Map<String, dynamic> queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
int businessId = 0,
|
int businessId = 0,
|
||||||
Map<String, String> additionalHeaders,
|
Map<String, String>? additionalHeaders,
|
||||||
Map<String, dynamic> body,
|
Map<String, dynamic>? body,
|
||||||
bool returnError = false,
|
bool returnError = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
@@ -320,8 +318,7 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
|
||||||
Box box = await Utils.getBox();
|
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
|
||||||
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
|
||||||
localHeaders['Http-App-Key'] = platformName;
|
localHeaders['Http-App-Key'] = platformName;
|
||||||
localHeaders['Http-Device-Type'] = platformName;
|
localHeaders['Http-Device-Type'] = platformName;
|
||||||
|
|
||||||
@@ -333,7 +330,9 @@ class HttpUtil {
|
|||||||
requestUrl = url;
|
requestUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.jsonPrettyPrint(body);
|
if (body != null) {
|
||||||
|
Utils.jsonPrettyPrint(body);
|
||||||
|
}
|
||||||
|
|
||||||
Dio dio = Dio();
|
Dio dio = Dio();
|
||||||
try {
|
try {
|
||||||
@@ -348,14 +347,14 @@ class HttpUtil {
|
|||||||
|
|
||||||
// Utils.jsonPrettyPrint(response.data);
|
// Utils.jsonPrettyPrint(response.data);
|
||||||
|
|
||||||
int statusCode = response.statusCode;
|
int statusCode = response.statusCode!;
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback(response);
|
callback(response);
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch(e) {
|
} on DioException catch(e) {
|
||||||
if (e.response != null) {
|
if (e.response != null) {
|
||||||
Utils.jsonPrettyPrint(e.response.data);
|
Utils.jsonPrettyPrint(e.response!.data);
|
||||||
}
|
}
|
||||||
if(returnError) {
|
if(returnError) {
|
||||||
return e;
|
return e;
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import '../vendor/stripe_sdk/lib/stripe_sdk.dart';
|
||||||
|
import '../vendor/stripe_sdk/lib/stripe_sdk_ui.dart';
|
||||||
|
|
||||||
|
import 'mini_stripe_api_handler.dart';
|
||||||
|
|
||||||
|
class MiniStripeApi {
|
||||||
|
static MiniStripeApi? _instance;
|
||||||
|
|
||||||
|
final MiniStripeApiHandler _apiHandler;
|
||||||
|
|
||||||
|
final String publishableKey;
|
||||||
|
|
||||||
|
final String apiVersion;
|
||||||
|
|
||||||
|
/// Create a new instance, which can be used with e.g. dependency injection.
|
||||||
|
/// Throws a [Exception] if an invalid [publishableKey] has been submitted.
|
||||||
|
///
|
||||||
|
/// [publishableKey] is your publishable key, beginning with "sk_".
|
||||||
|
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||||
|
///
|
||||||
|
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||||
|
/// This is a optional parameter.
|
||||||
|
MiniStripeApi(this.publishableKey, {this.apiVersion = defaultApiVersion, String? stripeAccount})
|
||||||
|
: _apiHandler = MiniStripeApiHandler(stripeAccount: stripeAccount) {
|
||||||
|
_validateKey(publishableKey);
|
||||||
|
_apiHandler.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the managed singleton instance of [StripeApi].
|
||||||
|
/// Afterwards you can use [StripeApi.instance] to access the created instance.
|
||||||
|
///
|
||||||
|
/// [publishableKey] is your publishable key, beginning with "sk_".
|
||||||
|
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||||
|
///
|
||||||
|
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||||
|
/// This is a optional parameter.
|
||||||
|
static void init(String publishableKey, {String apiVersion = defaultApiVersion, String? stripeAccount}) {
|
||||||
|
// _instance ??= MiniStripeApi(publishableKey, uuidString, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||||
|
_instance = MiniStripeApi(publishableKey, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the singleton instance of [StripeApi].
|
||||||
|
/// Throws an [Exception] if [StripeApi.init] hasn't been called previously.
|
||||||
|
static MiniStripeApi get instance {
|
||||||
|
if (_instance == null) {
|
||||||
|
throw Exception('Attempted to get singleton instance of StripeApi without initialization');
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a stripe Token
|
||||||
|
/// https://stripe.com/docs/api/tokens
|
||||||
|
Future<Map<String, dynamic>> createToken(Map<String, dynamic> data) async {
|
||||||
|
final path = '/tokens';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a PaymentMethod.
|
||||||
|
/// https://stripe.com/docs/api/payment_methods/create
|
||||||
|
Future<Map<String, dynamic>> createPaymentMethod(Map<String, dynamic> data) async {
|
||||||
|
final path = '/payment_methods';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a PaymentMethod from a card.
|
||||||
|
/// This will only create a PaymentMethod with the minimum required properties.
|
||||||
|
/// To include additional properties such as billing details, use [StripeCard.toPaymentMethod], add additional details
|
||||||
|
/// and then use [createPaymentMethod].
|
||||||
|
Future<Map<String, dynamic>> createPaymentMethodFromCard(StripeCard card) async {
|
||||||
|
return createPaymentMethod(card.toPaymentMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Source object.
|
||||||
|
/// https://stripe.com/docs/api/sources/create
|
||||||
|
Future<Map<String, dynamic>> createSource(Map<String, dynamic> data) async {
|
||||||
|
final path = '/sources';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a PaymentIntent.
|
||||||
|
/// https://stripe.com/docs/api/payment_intents/retrieve
|
||||||
|
Future<Map<String, dynamic>> retrievePaymentIntent(String clientSecret, {String? apiVersion}) async {
|
||||||
|
final intentId = _parseIdFromClientSecret(clientSecret);
|
||||||
|
final path = '/payment_intents/$intentId';
|
||||||
|
final params = {'client_secret': clientSecret};
|
||||||
|
return _apiHandler.request(RequestMethod.get, path, publishableKey, apiVersion ?? this.apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm a PaymentIntent
|
||||||
|
/// https://stripe.com/docs/api/payment_intents/confirm
|
||||||
|
Future<Map<String, dynamic>> confirmPaymentIntent(String clientSecret, {Map<String, dynamic>? data, String? idempotencyKey}) async {
|
||||||
|
final params = data ?? {};
|
||||||
|
final intent = _parseIdFromClientSecret(clientSecret);
|
||||||
|
params['client_secret'] = clientSecret;
|
||||||
|
final path = '/payment_intents/$intent/confirm';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: params, idempotencyKey: idempotencyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a SetupIntent.
|
||||||
|
/// https://stripe.com/docs/api/setup_intents/retrieve
|
||||||
|
Future<Map<String, dynamic>> retrieveSetupIntent(String clientSecret, {String? apiVersion}) async {
|
||||||
|
final intentId = _parseIdFromClientSecret(clientSecret);
|
||||||
|
final path = '/setup_intents/$intentId';
|
||||||
|
final params = {'client_secret': clientSecret};
|
||||||
|
return _apiHandler.request(RequestMethod.get, path, publishableKey, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm a SetupIntent
|
||||||
|
/// https://stripe.com/docs/api/setup_intents/confirm
|
||||||
|
Future<Map<String, dynamic>> confirmSetupIntent(String clientSecret, {Map<String, dynamic>? data}) async {
|
||||||
|
final params = data ?? {};
|
||||||
|
final intent = _parseIdFromClientSecret(clientSecret);
|
||||||
|
params['client_secret'] = clientSecret;
|
||||||
|
final path = '/setup_intents/$intent/confirm';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the received [publishableKey] and throws a [Exception] if an
|
||||||
|
/// invalid key has been submitted.
|
||||||
|
static void _validateKey(String publishableKey) {
|
||||||
|
if (publishableKey == null || publishableKey.isEmpty) {
|
||||||
|
throw Exception('Invalid Publishable Key: '
|
||||||
|
'You must use a valid publishable key to create a token. '
|
||||||
|
'For more info, see https://stripe.com/docs/stripe.js.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publishableKey.startsWith('sk_')) {
|
||||||
|
throw Exception('Invalid Publishable Key: '
|
||||||
|
'You are using a secret key to create a token, '
|
||||||
|
'instead of the publishable one. For more info, '
|
||||||
|
'see https://stripe.com/docs/stripe.js');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _parseIdFromClientSecret(String clientSecret) {
|
||||||
|
return clientSecret.split('_secret')[0];
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert' show json;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../vendor/stripe_sdk/lib/stripe_sdk.dart';
|
||||||
|
|
||||||
|
class MiniStripeApiHandler {
|
||||||
|
static const String LIVE_API_BASE = 'https://api.stripe.com';
|
||||||
|
static const String LIVE_LOGGING_BASE = 'https://q.stripe.com';
|
||||||
|
static const String LOGGING_ENDPOINT = 'https://m.stripe.com/4';
|
||||||
|
static const String LIVE_API_PATH = LIVE_API_BASE + '/v1';
|
||||||
|
|
||||||
|
static const String CHARSET = 'UTF-8';
|
||||||
|
static const String CUSTOMERS = 'customers';
|
||||||
|
static const String TOKENS = 'tokens';
|
||||||
|
static const String SOURCES = 'sources';
|
||||||
|
|
||||||
|
static const String HEADER_KEY_REQUEST_ID = 'Request-Id';
|
||||||
|
static const String FIELD_ERROR = 'error';
|
||||||
|
static const String FIELD_SOURCE = 'source';
|
||||||
|
|
||||||
|
String apiVersion = defaultApiVersion;
|
||||||
|
|
||||||
|
static const String MALFORMED_RESPONSE_MESSAGE = 'An improperly formatted error response was found.';
|
||||||
|
|
||||||
|
final http.Client _client = http.Client();
|
||||||
|
|
||||||
|
final String? stripeAccount;
|
||||||
|
|
||||||
|
MiniStripeApiHandler({this.stripeAccount});
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> request(RequestMethod method, String path, String key, String? apiVersion,
|
||||||
|
{final Map<String, dynamic>? params, String? idempotencyKey}) {
|
||||||
|
final options = RequestOptions(key: key, apiVersion: apiVersion, stripeAccount: stripeAccount, idempotencyKey: idempotencyKey);
|
||||||
|
return _getStripeResponse(method, LIVE_API_PATH + path, options, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _getStripeResponse(RequestMethod method, final String url, final RequestOptions options,
|
||||||
|
{final Map<String, dynamic>? params}) async {
|
||||||
|
print('url: $url');
|
||||||
|
final headers = _headers(options: options);
|
||||||
|
|
||||||
|
print('headers: $headers');
|
||||||
|
http.Response response;
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case RequestMethod.get:
|
||||||
|
var fUrl = url;
|
||||||
|
if (params != null && params.isNotEmpty) {
|
||||||
|
fUrl = '$url?${_encodeMap(params)}';
|
||||||
|
}
|
||||||
|
response = await _client.get(Uri.parse(fUrl), headers: headers);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestMethod.post:
|
||||||
|
response = await _client.post(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: headers,
|
||||||
|
body: params != null ? _urlEncodeMap(params) : null,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestMethod.delete:
|
||||||
|
response = await _client.delete(Uri.parse(url), headers: headers);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception('Request Method: $method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
final requestId = response.headers[HEADER_KEY_REQUEST_ID];
|
||||||
|
|
||||||
|
final statusCode = response.statusCode;
|
||||||
|
Map<String, dynamic> resp;
|
||||||
|
try {
|
||||||
|
resp = json.decode(response.body);
|
||||||
|
} catch (error) {
|
||||||
|
final stripeError = StripeApiError(requestId, {StripeApiError.fieldMessage: MALFORMED_RESPONSE_MESSAGE});
|
||||||
|
throw StripeApiException(stripeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode < 200 || statusCode >= 300) {
|
||||||
|
final Map<String, dynamic> errBody = resp[FIELD_ERROR];
|
||||||
|
final stripeError = StripeApiError(requestId, errBody);
|
||||||
|
throw StripeApiException(stripeError);
|
||||||
|
} else {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
static Map<String, String> _headers({RequestOptions? options}) {
|
||||||
|
final headers = <String, String>{};
|
||||||
|
headers['Accept-Charset'] = CHARSET;
|
||||||
|
headers['Accept'] = 'application/json';
|
||||||
|
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||||
|
headers['User-Agent'] = 'StripeSDK/v2';
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
headers['Authorization'] = 'Bearer ${options.key}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug headers
|
||||||
|
final propertyMap = <String, String>{};
|
||||||
|
propertyMap['os.name'] = defaultTargetPlatform.toString();
|
||||||
|
propertyMap['lang'] = 'Dart';
|
||||||
|
propertyMap['publisher'] = 'lars.dahl@gmail.com';
|
||||||
|
|
||||||
|
headers['X-Stripe-Client-User-Agent'] = json.encode(propertyMap);
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
if (options.apiVersion != null) {
|
||||||
|
headers['Stripe-Version'] = options.apiVersion!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.stripeAccount != null) {
|
||||||
|
headers['Stripe-Account'] = options.stripeAccount!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.idempotencyKey != null) {
|
||||||
|
headers['Idempotency-Key'] = options.idempotencyKey!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _encodeMap(Map<String, dynamic> params) {
|
||||||
|
return params.keys
|
||||||
|
.map((key) => '${Uri.encodeComponent(key)}=${Uri.encodeComponent(params[key].toString())}')
|
||||||
|
.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _urlEncodeMap(dynamic data) {
|
||||||
|
final urlData = StringBuffer('');
|
||||||
|
var first = true;
|
||||||
|
void urlEncode(dynamic sub, String path) {
|
||||||
|
if (sub is List) {
|
||||||
|
for (var i = 0; i < sub.length; i++) {
|
||||||
|
urlEncode(sub[i], '$path%5B%5D');
|
||||||
|
}
|
||||||
|
} else if (sub is Map) {
|
||||||
|
sub.forEach((k, v) {
|
||||||
|
if (path == '') {
|
||||||
|
urlEncode(v, '${Uri.encodeQueryComponent(k)}');
|
||||||
|
} else {
|
||||||
|
urlEncode(v, '$path%5B${Uri.encodeQueryComponent(k)}%5D');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!first) {
|
||||||
|
urlData.write('&');
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
urlData.write('$path=${Uri.encodeQueryComponent(sub.toString())}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urlEncode(data, '');
|
||||||
|
return urlData.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ class ShopScrollController extends ScrollController {
|
|||||||
this.coordinator, {
|
this.coordinator, {
|
||||||
double initialScrollOffset = 0.0,
|
double initialScrollOffset = 0.0,
|
||||||
this.keepScrollOffset = true,
|
this.keepScrollOffset = true,
|
||||||
this.debugLabel,
|
required this.debugLabel,
|
||||||
}) : assert(initialScrollOffset != null),
|
}) : assert(initialScrollOffset != null),
|
||||||
assert(keepScrollOffset != null),
|
assert(keepScrollOffset != null),
|
||||||
_initialScrollOffset = initialScrollOffset;
|
_initialScrollOffset = initialScrollOffset;
|
||||||
@@ -61,7 +61,7 @@ class ShopScrollController extends ScrollController {
|
|||||||
'ScrollController not attached to any scroll views.');
|
'ScrollController not attached to any scroll views.');
|
||||||
assert(_positions.length == 1,
|
assert(_positions.length == 1,
|
||||||
'ScrollController attached to multiple scroll views.');
|
'ScrollController attached to multiple scroll views.');
|
||||||
return _positions.single;
|
return _positions.single as ShopScrollPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 可滚动小部件的当前滚动偏移量
|
/// 可滚动小部件的当前滚动偏移量
|
||||||
@@ -83,12 +83,12 @@ class ShopScrollController extends ScrollController {
|
|||||||
/// 持续时间不能为零。 要在没有动画的情况下跳至特定值,请使用[jumpTo]。
|
/// 持续时间不能为零。 要在没有动画的情况下跳至特定值,请使用[jumpTo]。
|
||||||
Future<void> animateTo(
|
Future<void> animateTo(
|
||||||
double offset, {
|
double offset, {
|
||||||
@required Duration duration,
|
required Duration duration,
|
||||||
@required Curve curve,
|
required Curve curve,
|
||||||
}) {
|
}) {
|
||||||
assert(_positions.isNotEmpty,
|
assert(_positions.isNotEmpty,
|
||||||
'ScrollController not attached to any scroll views.');
|
'ScrollController not attached to any scroll views.');
|
||||||
final List<Future<void>> animations = List<Future<void>>(_positions.length);
|
final List<Future<void>> animations = List<Future<void>>.filled(_positions.length, Future<void>.value());
|
||||||
for (int i = 0; i < _positions.length; i += 1)
|
for (int i = 0; i < _positions.length; i += 1)
|
||||||
animations[i] =
|
animations[i] =
|
||||||
_positions[i].animateTo(offset, duration: duration, curve: curve);
|
_positions[i].animateTo(offset, duration: duration, curve: curve);
|
||||||
@@ -149,7 +149,7 @@ class ShopScrollController extends ScrollController {
|
|||||||
/// 则它将是前一个实例。 当环境已更改并且[Scrollable]需要重新创建[ScrollPosition]
|
/// 则它将是前一个实例。 当环境已更改并且[Scrollable]需要重新创建[ScrollPosition]
|
||||||
/// 对象时,将使用此方法。 第一次创建[ScrollPosition]时为null。
|
/// 对象时,将使用此方法。 第一次创建[ScrollPosition]时为null。
|
||||||
ScrollPosition createScrollPosition(ScrollPhysics physics,
|
ScrollPosition createScrollPosition(ScrollPhysics physics,
|
||||||
ScrollContext context, ScrollPosition oldPosition) {
|
ScrollContext context, ScrollPosition? oldPosition) {
|
||||||
return ShopScrollPosition(
|
return ShopScrollPosition(
|
||||||
coordinator: coordinator,
|
coordinator: coordinator,
|
||||||
physics: physics,
|
physics: physics,
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ class ShopScrollCoordinator {
|
|||||||
/// 页面主 CustomScrollView 控制
|
/// 页面主 CustomScrollView 控制
|
||||||
final String pageLabel = "page";
|
final String pageLabel = "page";
|
||||||
|
|
||||||
ShopScrollController _pageScrollController;
|
ShopScrollController? _pageScrollController;
|
||||||
double Function() pinnedHeaderSliverHeightBuilder;
|
double Function()? pinnedHeaderSliverHeightBuilder;
|
||||||
|
|
||||||
ShopScrollPosition get _pageScrollPosition => _pageScrollController.position;
|
ShopScrollPosition get _pageScrollPosition => _pageScrollController!.position;
|
||||||
|
|
||||||
ScrollDragController scrollDragController;
|
ScrollDragController? scrollDragController;
|
||||||
|
|
||||||
/// 主页面滑动部件默认位置
|
/// 主页面滑动部件默认位置
|
||||||
double _pageInitialOffset;
|
double? _pageInitialOffset;
|
||||||
|
|
||||||
/// 获取主页面滑动控制器
|
/// 获取主页面滑动控制器
|
||||||
ShopScrollController pageScrollController([double initialOffset = 0.0]) {
|
ShopScrollController pageScrollController([double initialOffset = 0.0]) {
|
||||||
@@ -28,27 +28,27 @@ class ShopScrollCoordinator {
|
|||||||
_pageInitialOffset = initialOffset;
|
_pageInitialOffset = initialOffset;
|
||||||
_pageScrollController = ShopScrollController(this,
|
_pageScrollController = ShopScrollController(this,
|
||||||
debugLabel: pageLabel, initialScrollOffset: initialOffset);
|
debugLabel: pageLabel, initialScrollOffset: initialOffset);
|
||||||
return _pageScrollController;
|
return _pageScrollController!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 创建并获取一个子滑动控制器
|
/// 创建并获取一个子滑动控制器
|
||||||
ShopScrollController newChildScrollController([String debugLabel]) =>
|
ShopScrollController newChildScrollController([String? debugLabel]) =>
|
||||||
ShopScrollController(this, debugLabel: debugLabel);
|
ShopScrollController(this, debugLabel: debugLabel ?? '');
|
||||||
|
|
||||||
/// 子部件滑动数据协调
|
/// 子部件滑动数据协调
|
||||||
/// [userScrollDirection]用户滑动方向
|
/// [userScrollDirection]用户滑动方向
|
||||||
/// [position]被滑动的子部件的位置信息
|
/// [position]被滑动的子部件的位置信息
|
||||||
void applyUserOffset(double delta,
|
void applyUserOffset(double delta,
|
||||||
[ScrollDirection userScrollDirection, ShopScrollPosition position]) {
|
[ScrollDirection? userScrollDirection, ShopScrollPosition? position]) {
|
||||||
if (userScrollDirection == ScrollDirection.reverse) {
|
if (userScrollDirection == ScrollDirection.reverse) {
|
||||||
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
|
updateUserScrollDirection(_pageScrollPosition, userScrollDirection!);
|
||||||
final innerDelta = _pageScrollPosition.applyClampedDragUpdate(delta);
|
final innerDelta = _pageScrollPosition.applyClampedDragUpdate(delta);
|
||||||
if (innerDelta != 0.0) {
|
if (innerDelta != 0.0) {
|
||||||
updateUserScrollDirection(position, userScrollDirection);
|
updateUserScrollDirection(position!, userScrollDirection);
|
||||||
position.applyFullDragUpdate(innerDelta);
|
position.applyFullDragUpdate(innerDelta);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateUserScrollDirection(position, userScrollDirection);
|
updateUserScrollDirection(position!, userScrollDirection!);
|
||||||
final outerDelta = position.applyClampedDragUpdate(delta);
|
final outerDelta = position.applyClampedDragUpdate(delta);
|
||||||
if (outerDelta != 0.0) {
|
if (outerDelta != 0.0) {
|
||||||
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
|
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
|
||||||
@@ -60,7 +60,7 @@ class ShopScrollCoordinator {
|
|||||||
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent,
|
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent,
|
||||||
ShopScrollPosition position) {
|
ShopScrollPosition position) {
|
||||||
if (pinnedHeaderSliverHeightBuilder != null) {
|
if (pinnedHeaderSliverHeightBuilder != null) {
|
||||||
maxScrollExtent = maxScrollExtent - pinnedHeaderSliverHeightBuilder();
|
maxScrollExtent = maxScrollExtent - pinnedHeaderSliverHeightBuilder!();
|
||||||
maxScrollExtent = math.max(0.0, maxScrollExtent);
|
maxScrollExtent = math.max(0.0, maxScrollExtent);
|
||||||
}
|
}
|
||||||
return position.applyContentDimensions(
|
return position.applyContentDimensions(
|
||||||
@@ -77,9 +77,9 @@ class ShopScrollCoordinator {
|
|||||||
/// 当手指离开屏幕
|
/// 当手指离开屏幕
|
||||||
void onPointerUp(PointerUpEvent event) {
|
void onPointerUp(PointerUpEvent event) {
|
||||||
final double _pagePixels = _pageScrollPosition.pixels;
|
final double _pagePixels = _pageScrollPosition.pixels;
|
||||||
if (0.0 < _pagePixels && _pagePixels < _pageInitialOffset) {
|
if (0.0 < _pagePixels && _pagePixels < _pageInitialOffset!) {
|
||||||
if (pageExpand == PageExpandState.NotExpand &&
|
if (pageExpand == PageExpandState.NotExpand &&
|
||||||
_pageInitialOffset - _pagePixels > _scrollRedundancy) {
|
_pageInitialOffset! - _pagePixels > _scrollRedundancy) {
|
||||||
_pageScrollPosition
|
_pageScrollPosition
|
||||||
.animateTo(0.0,
|
.animateTo(0.0,
|
||||||
duration: const Duration(milliseconds: 400), curve: Curves.ease)
|
duration: const Duration(milliseconds: 400), curve: Curves.ease)
|
||||||
@@ -87,7 +87,7 @@ class ShopScrollCoordinator {
|
|||||||
} else {
|
} else {
|
||||||
pageExpand = PageExpandState.Expanding;
|
pageExpand = PageExpandState.Expanding;
|
||||||
_pageScrollPosition
|
_pageScrollPosition
|
||||||
.animateTo(_pageInitialOffset,
|
.animateTo(_pageInitialOffset!,
|
||||||
duration: const Duration(milliseconds: 400), curve: Curves.ease)
|
duration: const Duration(milliseconds: 400), curve: Curves.ease)
|
||||||
.then((value) => pageExpand = PageExpandState.NotExpand);
|
.then((value) => pageExpand = PageExpandState.NotExpand);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import 'shop_scroll_coordinator.dart';
|
|||||||
class ShopScrollPosition extends ScrollPosition
|
class ShopScrollPosition extends ScrollPosition
|
||||||
implements ScrollActivityDelegate {
|
implements ScrollActivityDelegate {
|
||||||
final ShopScrollCoordinator coordinator; // 协调器
|
final ShopScrollCoordinator coordinator; // 协调器
|
||||||
ScrollDragController _currentDrag;
|
ScrollDragController? _currentDrag;
|
||||||
double _heldPreviousVelocity = 0.0;
|
double _heldPreviousVelocity = 0.0;
|
||||||
|
|
||||||
ShopScrollPosition(
|
ShopScrollPosition(
|
||||||
{@required ScrollPhysics physics,
|
{required ScrollPhysics physics,
|
||||||
@required ScrollContext context,
|
required ScrollContext context,
|
||||||
double initialPixels = 0.0,
|
double initialPixels = 0.0,
|
||||||
bool keepScrollOffset = true,
|
bool keepScrollOffset = true,
|
||||||
ScrollPosition oldPosition,
|
ScrollPosition? oldPosition,
|
||||||
String debugLabel,
|
String? debugLabel,
|
||||||
@required this.coordinator})
|
required this.coordinator})
|
||||||
: super(
|
: super(
|
||||||
physics: physics,
|
physics: physics,
|
||||||
context: context,
|
context: context,
|
||||||
@@ -40,7 +40,7 @@ class ShopScrollPosition extends ScrollPosition
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
double setPixels(double newPixels) {
|
double setPixels(double newPixels) {
|
||||||
assert(activity.isScrolling);
|
assert(activity!.isScrolling);
|
||||||
return super.setPixels(newPixels);
|
return super.setPixels(newPixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +51,13 @@ class ShopScrollPosition extends ScrollPosition
|
|||||||
goIdle();
|
goIdle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activity.updateDelegate(this);
|
activity!.updateDelegate(this);
|
||||||
final ShopScrollPosition typedOther = other as ShopScrollPosition;
|
final ShopScrollPosition typedOther = other as ShopScrollPosition;
|
||||||
_userScrollDirection = typedOther._userScrollDirection;
|
_userScrollDirection = typedOther._userScrollDirection;
|
||||||
assert(_currentDrag == null);
|
assert(_currentDrag == null);
|
||||||
if (typedOther._currentDrag != null) {
|
if (typedOther._currentDrag != null) {
|
||||||
_currentDrag = typedOther._currentDrag;
|
_currentDrag = typedOther._currentDrag;
|
||||||
_currentDrag.updateDelegate(this);
|
_currentDrag?.updateDelegate(this);
|
||||||
typedOther._currentDrag = null;
|
typedOther._currentDrag = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,14 +131,14 @@ class ShopScrollPosition extends ScrollPosition
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void beginActivity(ScrollActivity newActivity) {
|
void beginActivity(ScrollActivity? newActivity) {
|
||||||
_heldPreviousVelocity = 0.0;
|
_heldPreviousVelocity = 0.0;
|
||||||
if (newActivity == null) return;
|
if (newActivity == null) return;
|
||||||
assert(newActivity.delegate == this);
|
assert(newActivity.delegate == this);
|
||||||
super.beginActivity(newActivity);
|
super.beginActivity(newActivity);
|
||||||
_currentDrag?.dispose();
|
_currentDrag?.dispose();
|
||||||
_currentDrag = null;
|
_currentDrag = null;
|
||||||
if (!activity.isScrolling) updateUserScrollDirection(ScrollDirection.idle);
|
if (!activity!.isScrolling) updateUserScrollDirection(ScrollDirection.idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 将[用户滚动方向]设置为给定值。
|
/// 将[用户滚动方向]设置为给定值。
|
||||||
@@ -154,7 +154,7 @@ class ShopScrollPosition extends ScrollPosition
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ScrollHoldController hold(VoidCallback holdCancelCallback) {
|
ScrollHoldController hold(VoidCallback holdCancelCallback) {
|
||||||
final double previousVelocity = activity.velocity;
|
final double previousVelocity = activity!.velocity;
|
||||||
final HoldScrollActivity holdActivity =
|
final HoldScrollActivity holdActivity =
|
||||||
HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback);
|
HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback);
|
||||||
beginActivity(holdActivity);
|
beginActivity(holdActivity);
|
||||||
@@ -195,10 +195,10 @@ class ShopScrollPosition extends ScrollPosition
|
|||||||
if (coordinator.pageExpand == PageExpandState.Expanding) return;
|
if (coordinator.pageExpand == PageExpandState.Expanding) return;
|
||||||
}
|
}
|
||||||
assert(pixels != null);
|
assert(pixels != null);
|
||||||
final Simulation simulation =
|
final Simulation? simulation =
|
||||||
physics.createBallisticSimulation(this, velocity);
|
physics.createBallisticSimulation(this, velocity);
|
||||||
if (simulation != null) {
|
if (simulation != null) {
|
||||||
beginActivity(BallisticScrollActivity(this, simulation, context.vsync));
|
beginActivity(BallisticScrollActivity(this, simulation, context.vsync, true));
|
||||||
} else {
|
} else {
|
||||||
goIdle();
|
goIdle();
|
||||||
}
|
}
|
||||||
@@ -220,8 +220,8 @@ class ShopScrollPosition extends ScrollPosition
|
|||||||
@override
|
@override
|
||||||
Future<void> animateTo(
|
Future<void> animateTo(
|
||||||
double to, {
|
double to, {
|
||||||
@required Duration duration,
|
required Duration duration,
|
||||||
@required Curve curve,
|
required Curve curve,
|
||||||
}) {
|
}) {
|
||||||
if (nearEqual(to, pixels, physics.tolerance.distance)) {
|
if (nearEqual(to, pixels, physics.tolerance.distance)) {
|
||||||
// 跳过动画,直接移到我们已经靠近的位置。
|
// 跳过动画,直接移到我们已经靠近的位置。
|
||||||
|
|||||||
+22
-183
@@ -6,15 +6,11 @@ import 'dart:ui' as ui;
|
|||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
// import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:stripe_payment/stripe_payment.dart';
|
import 'package:stripe_payment/stripe_payment.dart';
|
||||||
|
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
@@ -42,174 +38,17 @@ class Util {
|
|||||||
}
|
}
|
||||||
Util._internal();
|
Util._internal();
|
||||||
|
|
||||||
// final FirebaseMessaging firebaseMessaging = FirebaseMessaging();
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
|
||||||
static bool notificationTapped = false;
|
static bool notificationTapped = false;
|
||||||
|
|
||||||
init() {
|
static Widget showImage(String imageUrl, {double? width, double? height,
|
||||||
// initLocalNotification();
|
BoxFit? fit, Widget Function(BuildContext, String, dynamic)? errorWidget}) {
|
||||||
// initFirebaseMessaging();
|
if (imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
|
||||||
|
|
||||||
// ePmqRwRUTmKuFNfbHA-6zq:APA91bEFEQx1YgJqfx0V0VYo86uRDGjG6SP1SPCAZW4OvQ7gMOeKWDQ47bfuEM00YavtKq7ZGpsWfFL7B3ypm00ZA6crjJJLK_-j_H8bS_dnQbLFwyRcW67IXCs5sRkro71bRZv7L1WM
|
|
||||||
|
|
||||||
// eventBus.on<SubscribeToTopic>().listen((event) {
|
|
||||||
// if (firebaseMessaging != null) {
|
|
||||||
// firebaseMessaging.subscribeToTopic(event.topic);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// eventBus.on<UnSubscribeToTopic>().listen((event) {
|
|
||||||
// if (firebaseMessaging != null) {
|
|
||||||
// firebaseMessaging.unsubscribeFromTopic(event.topic);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
initLocalNotification() async {
|
|
||||||
var initializationSettingsAndroid = new AndroidInitializationSettings('ic_launcher');
|
|
||||||
var initializationSettingsIOS = new IOSInitializationSettings(
|
|
||||||
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
|
|
||||||
var initializationSettings = new InitializationSettings(
|
|
||||||
android: initializationSettingsAndroid,
|
|
||||||
iOS: initializationSettingsIOS,
|
|
||||||
);
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
|
|
||||||
onSelectNotification: onSelectNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future onSelectNotification(String payload, {bool replace=false}) async {
|
|
||||||
print('payload: $payload');
|
|
||||||
if (payload != null && payload.isNotEmpty) {
|
|
||||||
Routes.router.navigateTo(store.state.context, payload, replace: replace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initFirebaseMessaging() {
|
|
||||||
// if (Platform.isIOS) {
|
|
||||||
// iosPermission();
|
|
||||||
// }
|
|
||||||
// firebaseMessaging.getToken().then((token) {
|
|
||||||
// print('FCM token: $token');
|
|
||||||
// store.dispatch(UpdateFcmToken(token));
|
|
||||||
// });
|
|
||||||
// firebaseMessaging.configure(
|
|
||||||
// onMessage: (Map<String, dynamic> message) {
|
|
||||||
// print('FCM onMessage: $message');
|
|
||||||
// // eventBus.fire(OnFcmReceived(message));
|
|
||||||
// showNotificationWithDefaultSound(message);
|
|
||||||
// return;
|
|
||||||
// },
|
|
||||||
// onBackgroundMessage: Platform.isIOS ? null : backgroundMessageHandler,
|
|
||||||
// onResume: (Map<String, dynamic> message) {
|
|
||||||
// print('FCM onResume: $message');
|
|
||||||
// if (Platform.isAndroid) {
|
|
||||||
// if (message != null && message['data'] != null && message['data']['route'] != null) {
|
|
||||||
// notificationTapped = true;
|
|
||||||
// onSelectNotification(message['data']['route']);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if (message != null && message['route'] != null) {
|
|
||||||
// notificationTapped = true;
|
|
||||||
// onSelectNotification(message['route']);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return;
|
|
||||||
// },
|
|
||||||
// onLaunch: (Map<String, dynamic> message) {
|
|
||||||
// print('FCM onLaunch: $message');
|
|
||||||
// if (Platform.isAndroid) {
|
|
||||||
// if (message != null && message['data'] != null && message['data']['route'] != null) {
|
|
||||||
// notificationTapped = true;
|
|
||||||
// onSelectNotification(message['data']['route'], replace: true);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if (message != null && message['route'] != null) {
|
|
||||||
// notificationTapped = true;
|
|
||||||
// onSelectNotification(message['route'], replace: true);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return;
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future onDidReceiveLocalNotification(int id, String title, String body, String payload) async {
|
|
||||||
showDialog(
|
|
||||||
context: store.state.context,
|
|
||||||
builder: (BuildContext context) => CupertinoAlertDialog(
|
|
||||||
title: Text(title),
|
|
||||||
content: Text(body),
|
|
||||||
actions: [
|
|
||||||
CupertinoDialogAction(
|
|
||||||
isDefaultAction: true,
|
|
||||||
child: Text(S.of(context).ok),
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.of(context, rootNavigator: true).pop();
|
|
||||||
if (payload != null && payload.isNotEmpty) {
|
|
||||||
Routes.router.navigateTo(context, payload);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future showNotificationWithDefaultSound(Map<String, dynamic> message) async {
|
|
||||||
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
|
|
||||||
'channelId',
|
|
||||||
'channelName',
|
|
||||||
'channelDescription',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
);
|
|
||||||
var iOSPlatformChannelSpecifics = new IOSNotificationDetails(presentSound: true, badgeNumber: 1);
|
|
||||||
var platformChannelSpecifics = new NotificationDetails(
|
|
||||||
android: androidPlatformChannelSpecifics,
|
|
||||||
iOS: iOSPlatformChannelSpecifics,
|
|
||||||
);
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
0,
|
|
||||||
Platform.isAndroid ? message['notification']['title'] : message['title'],
|
|
||||||
Platform.isAndroid ? message['notification']['body'] : message['body'],
|
|
||||||
platformChannelSpecifics,
|
|
||||||
payload: Platform.isAndroid ? message['data']['route'] : message['route'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// iosPermission() {
|
|
||||||
// firebaseMessaging.requestNotificationPermissions(
|
|
||||||
// const IosNotificationSettings(
|
|
||||||
// sound: true,
|
|
||||||
// badge: true,
|
|
||||||
// alert: true,
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// firebaseMessaging.onIosSettingsRegistered.listen((IosNotificationSettings settings) {
|
|
||||||
// print('IOS settings registered: $settings');
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
static Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
|
|
||||||
print('FCM background message handler: $message');
|
|
||||||
return Future<void>.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<Box> getBox() async {
|
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
|
||||||
Hive.init(dir.path);
|
|
||||||
Box box = await Hive.openBox('app_data');
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Widget showImage(String imageUrl, {double width, double height,
|
|
||||||
BoxFit fit, Widget Function(BuildContext, String, dynamic) errorWidget}) {
|
|
||||||
if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
|
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
width: width,
|
width: width,
|
||||||
height: width,
|
height: width,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
placeholder: (context, url) => Utils.imageLoadingIndicator(width: width, height: height),
|
placeholder: (context, url) => Utils.imageLoadingIndicator(width: width ?? 30, height: height ?? 30),
|
||||||
errorWidget: errorWidget != null ? errorWidget : (context, url, error) {
|
errorWidget: errorWidget != null ? errorWidget : (context, url, error) {
|
||||||
return Image.asset(
|
return Image.asset(
|
||||||
'assets/images/not_found.png',
|
'assets/images/not_found.png',
|
||||||
@@ -219,13 +58,13 @@ class Util {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (imageUrl != null && imageUrl.isNotEmpty) {
|
} else if (imageUrl.isNotEmpty) {
|
||||||
return Image.file(
|
return Image.file(
|
||||||
File(imageUrl),
|
File(imageUrl),
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) {
|
errorBuilder: (BuildContext context, Object object, StackTrace? stackTrace) {
|
||||||
return Image.asset(
|
return Image.asset(
|
||||||
'assets/images/not_found.png',
|
'assets/images/not_found.png',
|
||||||
width: width,
|
width: width,
|
||||||
@@ -319,14 +158,14 @@ class Util {
|
|||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
var image = await picker.pickImage(source: ImageSource.gallery);
|
var image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
uploadPicture(context, File(image.path), user, commentId: commentId, orderId: orderId);
|
uploadPicture(context, File(image!.path), user, commentId: commentId, orderId: orderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getPictureFromCamera(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
void getPictureFromCamera(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
var image = await picker.pickImage(source: ImageSource.camera);
|
var image = await picker.pickImage(source: ImageSource.camera);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
uploadPicture(context, File(image.path), user, commentId: commentId, orderId: orderId);
|
uploadPicture(context, File(image!.path), user, commentId: commentId, orderId: orderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uploadPicture(BuildContext context, File image, User user, {int commentId = -1, int orderId = 0}) async {
|
void uploadPicture(BuildContext context, File image, User user, {int commentId = -1, int orderId = 0}) async {
|
||||||
@@ -456,18 +295,18 @@ class Util {
|
|||||||
ImagePicker picker = ImagePicker();
|
ImagePicker picker = ImagePicker();
|
||||||
var image = await picker.pickImage(source: ImageSource.gallery);
|
var image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
onGotFile(imageId, image.path);
|
onGotFile(imageId, image!.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getPictureFromCamera2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
void getPictureFromCamera2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
||||||
ImagePicker picker = ImagePicker();
|
ImagePicker picker = ImagePicker();
|
||||||
var image = await picker.pickImage(source: ImageSource.camera);
|
var image = await picker.pickImage(source: ImageSource.camera);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
onGotFile(imageId, image.path);
|
onGotFile(imageId, image!.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createTicket(BuildContext context, String msg, List<Map<String, dynamic>> images,
|
Future<void> createTicket(BuildContext context, String msg, List<Map<String, dynamic>> images,
|
||||||
OnSuccess onSuccess, OnError onError, {int id}) {
|
OnSuccess onSuccess, OnError onError, {int? id}) async {
|
||||||
var formData = FormData();
|
var formData = FormData();
|
||||||
formData.fields.add(MapEntry("msg", msg));
|
formData.fields.add(MapEntry("msg", msg));
|
||||||
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
|
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
|
||||||
@@ -562,7 +401,7 @@ class Util {
|
|||||||
PaymentPlatform paymentPlatform, {
|
PaymentPlatform paymentPlatform, {
|
||||||
bool googlePay=false,
|
bool googlePay=false,
|
||||||
bool applePay=false,
|
bool applePay=false,
|
||||||
StripePaymentMethod stripePaymentMethod,
|
StripePaymentMethod? stripePaymentMethod,
|
||||||
}) {
|
}) {
|
||||||
switch(paymentPlatform.code) {
|
switch(paymentPlatform.code) {
|
||||||
case Constants.PAYMENT_METHOD_CODE_SQUARE:
|
case Constants.PAYMENT_METHOD_CODE_SQUARE:
|
||||||
@@ -587,7 +426,7 @@ class Util {
|
|||||||
StripePayment.setOptions(
|
StripePayment.setOptions(
|
||||||
StripeOptions(publishableKey: paymentPlatform.publishableKey,
|
StripeOptions(publishableKey: paymentPlatform.publishableKey,
|
||||||
merchantId: paymentPlatform.merchantId,
|
merchantId: paymentPlatform.merchantId,
|
||||||
androidPayMode: paymentPlatform.publishableKey.contains('_test_')
|
androidPayMode: paymentPlatform.publishableKey!.contains('_test_')
|
||||||
? 'test'
|
? 'test'
|
||||||
: 'production'
|
: 'production'
|
||||||
)
|
)
|
||||||
@@ -595,14 +434,14 @@ class Util {
|
|||||||
StripePayment.paymentRequestWithNativePay(
|
StripePayment.paymentRequestWithNativePay(
|
||||||
androidPayOptions: AndroidPayPaymentRequest(
|
androidPayOptions: AndroidPayPaymentRequest(
|
||||||
totalPrice: order.totalPrice.toStringAsFixed(2),
|
totalPrice: order.totalPrice.toStringAsFixed(2),
|
||||||
currencyCode: order.businessInfo.currency,
|
currencyCode: order.businessInfo!.currency,
|
||||||
),
|
),
|
||||||
applePayOptions: ApplePayPaymentOptions(
|
applePayOptions: ApplePayPaymentOptions(
|
||||||
currencyCode: order.businessInfo.currency,
|
currencyCode: order.businessInfo!.currency,
|
||||||
countryCode: order.businessInfo.address.country,
|
countryCode: order.businessInfo!.address.country,
|
||||||
items: [
|
items: [
|
||||||
ApplePayItem(
|
ApplePayItem(
|
||||||
label: order.businessInfo.name,
|
label: order.businessInfo!.name,
|
||||||
amount: order.totalPrice.toStringAsFixed(2),
|
amount: order.totalPrice.toStringAsFixed(2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -633,7 +472,7 @@ class Util {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (paymentMethod != null) {
|
if (paymentMethod != null) {
|
||||||
Utils.stripePaymentIntent(order, null, paymentMethod.id, paymentMethod.type, (response) {
|
Utils.stripePaymentIntent(order, null, paymentMethod.id!, paymentMethod.type!, (response) {
|
||||||
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
|
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
|
||||||
StripePayment.confirmPaymentIntent(
|
StripePayment.confirmPaymentIntent(
|
||||||
PaymentIntent(
|
PaymentIntent(
|
||||||
@@ -643,8 +482,8 @@ class Util {
|
|||||||
).then((paymentIntentResult) {
|
).then((paymentIntentResult) {
|
||||||
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
|
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
|
||||||
Utils.stripeChargedSuccess(order,
|
Utils.stripeChargedSuccess(order,
|
||||||
paymentMethod.id,
|
paymentMethod.id!,
|
||||||
paymentIntentResult.paymentIntentId,
|
paymentIntentResult.paymentIntentId!,
|
||||||
(response) {
|
(response) {
|
||||||
StripePayment.completeNativePayRequest().then((_) {
|
StripePayment.completeNativePayRequest().then((_) {
|
||||||
eventBus.fire(OnOrderUpdated());
|
eventBus.fire(OnOrderUpdated());
|
||||||
@@ -680,10 +519,10 @@ class Util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Uint8List> getBytesFromAsset(String path, int width) async {
|
static Future<Uint8List?> getBytesFromAsset(String path, int width) async {
|
||||||
ByteData data = await rootBundle.load(path);
|
ByteData data = await rootBundle.load(path);
|
||||||
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
|
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
|
||||||
ui.FrameInfo fi = await codec.getNextFrame();
|
ui.FrameInfo fi = await codec.getNextFrame();
|
||||||
return (await fi.image.toByteData(format: ui.ImageByteFormat.png)).buffer.asUint8List();
|
return (await fi.image.toByteData(format: ui.ImageByteFormat.png))?.buffer.asUint8List();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+22
-30
@@ -5,8 +5,6 @@ import 'dart:typed_data';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
@@ -36,27 +34,21 @@ class Util {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Box> getBox() async {
|
static Widget showImage(String imageUrl, {double? width, double? height,
|
||||||
Hive.initFlutter();
|
BoxFit? fit, Widget Function(BuildContext, String?, dynamic)? errorWidget}) {
|
||||||
Box box = await Hive.openBox('wisetronic_app_data');
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Widget showImage(String imageUrl, {double width, double height,
|
|
||||||
BoxFit fit, Widget Function(BuildContext, String, dynamic) errorWidget}) {
|
|
||||||
return Image.network(imageUrl,
|
return Image.network(imageUrl,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
cacheWidth: width != null ? width.round() : null,
|
cacheWidth: width != null ? width.round() : null,
|
||||||
cacheHeight: height != null ? height.round() : null,
|
cacheHeight: height != null ? height.round() : null,
|
||||||
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
|
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||||
if (loadingProgress == null) return child;
|
if (loadingProgress == null) return child;
|
||||||
return Center(
|
return Center(
|
||||||
child: Utils.imageLoadingIndicator(),
|
child: Utils.imageLoadingIndicator(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) {
|
errorBuilder: (BuildContext context, Object object, StackTrace? stackTrace) {
|
||||||
print('StackTrace: $stackTrace');
|
print('StackTrace: $stackTrace');
|
||||||
if (errorWidget != null) {
|
if (errorWidget != null) {
|
||||||
return errorWidget(context, null, null);
|
return errorWidget(context, null, null);
|
||||||
@@ -106,16 +98,16 @@ class Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void startFilePicker(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
void startFilePicker(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
||||||
InputElement uploadInput = FileUploadInputElement();
|
InputElement uploadInput = FileUploadInputElement() as InputElement;
|
||||||
uploadInput.click();
|
uploadInput.click();
|
||||||
uploadInput.onChange.listen((e) {
|
uploadInput.onChange.listen((e) {
|
||||||
final files = uploadInput.files;
|
final files = uploadInput.files;
|
||||||
if (files.length >= 1) {
|
if (files!.length >= 1) {
|
||||||
final File file = files[0];
|
final File file = files[0];
|
||||||
final FileReader reader = new FileReader();
|
final FileReader reader = new FileReader();
|
||||||
|
|
||||||
reader.onLoadEnd.listen((e) {
|
reader.onLoadEnd.listen((e) {
|
||||||
uploadPicture(context, reader.result, file.name, user, commentId: commentId, orderId: orderId);
|
uploadPicture(context, reader.result as Uint8List, file.name, user, commentId: commentId, orderId: orderId);
|
||||||
});
|
});
|
||||||
reader.onError.listen((e) {
|
reader.onError.listen((e) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
@@ -210,11 +202,11 @@ class Util {
|
|||||||
|
|
||||||
void startFilePicker2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
void startFilePicker2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
||||||
final _image = await WebImagePicker().pickImage();
|
final _image = await WebImagePicker().pickImage();
|
||||||
onGotFile(imageId, _image.dataScheme);
|
onGotFile(imageId, _image!.dataScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createTicket(BuildContext context, String msg, List<Map<String, dynamic>> images,
|
void createTicket(BuildContext context, String msg, List<Map<String, dynamic>> images,
|
||||||
OnSuccess onSuccess, OnError onError, {int id}) {
|
OnSuccess onSuccess, OnError onError, {int? id}) {
|
||||||
var formData = FormData();
|
var formData = FormData();
|
||||||
formData.fields.add(MapEntry("msg", msg));
|
formData.fields.add(MapEntry("msg", msg));
|
||||||
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
|
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
|
||||||
@@ -252,7 +244,7 @@ class Util {
|
|||||||
PaymentPlatform paymentPlatform, {
|
PaymentPlatform paymentPlatform, {
|
||||||
bool googlePay=false,
|
bool googlePay=false,
|
||||||
bool applePay=false,
|
bool applePay=false,
|
||||||
StripePaymentMethod stripePaymentMethod,
|
StripePaymentMethod? stripePaymentMethod,
|
||||||
}) {
|
}) {
|
||||||
switch(paymentPlatform.code) {
|
switch(paymentPlatform.code) {
|
||||||
case Constants.PAYMENT_METHOD_CODE_SQUARE:
|
case Constants.PAYMENT_METHOD_CODE_SQUARE:
|
||||||
@@ -293,36 +285,36 @@ class Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ImageInfo {
|
class ImageInfo {
|
||||||
String name;
|
String name = '';
|
||||||
String data;
|
String data = '';
|
||||||
String dataScheme;
|
String dataScheme = '';
|
||||||
String path;
|
String path = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebImagePicker {
|
class WebImagePicker {
|
||||||
Future<ImageInfo> pickImage() async {
|
Future<ImageInfo?> pickImage() async {
|
||||||
print('pickImage');
|
print('pickImage');
|
||||||
final ImageInfo data = ImageInfo();
|
final ImageInfo data = ImageInfo();
|
||||||
final FileUploadInputElement input = FileUploadInputElement();
|
final FileUploadInputElement input = FileUploadInputElement();
|
||||||
document.body.children.add(input);
|
document.body!.children.add(input);
|
||||||
input..accept = 'image/*';
|
input..accept = 'image/*';
|
||||||
input.click();
|
input.click();
|
||||||
await input.onChange.first;
|
await input.onChange.first;
|
||||||
if (input.files.isEmpty) return null;
|
if (input.files!.isEmpty) return null;
|
||||||
final reader = FileReader();
|
final reader = FileReader();
|
||||||
reader.readAsDataUrl(input.files[0]);
|
reader.readAsDataUrl(input.files![0]);
|
||||||
await reader.onLoad.first;
|
await reader.onLoad.first;
|
||||||
final encoded = reader.result as String;
|
final encoded = reader.result as String;
|
||||||
// remove data:image/*;base64 preambule
|
// remove data:image/*;base64 preambule
|
||||||
final stripped =
|
final stripped =
|
||||||
encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), '');
|
encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), '');
|
||||||
//final imageBase64 = base64.decode(stripped);
|
//final imageBase64 = base64.decode(stripped);
|
||||||
final imageName = input.files?.first?.name;
|
final imageName = input.files?.first.name;
|
||||||
final imagePath = input.files?.first?.relativePath;
|
final imagePath = input.files?.first.relativePath;
|
||||||
data.name = imageName;
|
data.name = imageName!;
|
||||||
data.data = stripped;
|
data.data = stripped;
|
||||||
data.dataScheme = encoded;
|
data.dataScheme = encoded;
|
||||||
data.path = imagePath;
|
data.path = imagePath!;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+145
-160
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
import 'package:flutter_wisetronic/models/product.dart';
|
import 'package:flutter_wisetronic/models/product.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:universal_io/io.dart';
|
import 'package:universal_io/io.dart';
|
||||||
@@ -28,6 +27,7 @@ import '../store/actions.dart';
|
|||||||
import '../store/store.dart';
|
import '../store/store.dart';
|
||||||
import 'http_util.dart';
|
import 'http_util.dart';
|
||||||
import 'util_web.dart' if (dart.library.io) 'util_io.dart';
|
import 'util_web.dart' if (dart.library.io) 'util_io.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
typedef void OnSuccess(Response response);
|
typedef void OnSuccess(Response response);
|
||||||
typedef void OnError(dynamic error);
|
typedef void OnError(dynamic error);
|
||||||
@@ -37,16 +37,25 @@ typedef void OnOk();
|
|||||||
typedef void OnLoadImageError();
|
typedef void OnLoadImageError();
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
static StreamSubscription onProductWillAddToCartSubscription;
|
static final Utils _utils = Utils._internal();
|
||||||
static StreamSubscription onProductWillRemoveFromCartSubscription;
|
static SharedPreferences? prefs;
|
||||||
|
|
||||||
|
static StreamSubscription? onProductWillAddToCartSubscription;
|
||||||
|
static StreamSubscription? onProductWillRemoveFromCartSubscription;
|
||||||
|
|
||||||
|
factory Utils() {
|
||||||
|
return _utils;
|
||||||
|
}
|
||||||
|
Utils._internal();
|
||||||
|
|
||||||
init() async {
|
init() async {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
onProductWillAddToCartSubscription =
|
onProductWillAddToCartSubscription =
|
||||||
eventBus.on<OnProductWillAddToCart>().listen((event) {
|
eventBus.on<OnProductWillAddToCart>().listen((event) {
|
||||||
CartInfo cartInfo =
|
CartInfo? cartInfo =
|
||||||
getCartInfoByBusiness(store.state.cartInfos, event.business);
|
getCartInfoByBusiness(store.state.cartInfos, event.business);
|
||||||
|
|
||||||
if (cartInfo == null || cartInfo.productList.length == 0) {
|
if (cartInfo == null || cartInfo.productList!.length == 0) {
|
||||||
cartInfo = CartInfo();
|
cartInfo = CartInfo();
|
||||||
cartInfo.id = 0;
|
cartInfo.id = 0;
|
||||||
cartInfo.amountPaid = 0.0;
|
cartInfo.amountPaid = 0.0;
|
||||||
@@ -57,7 +66,7 @@ class Utils {
|
|||||||
id: 0,
|
id: 0,
|
||||||
price: event.price,
|
price: event.price,
|
||||||
product: event.product,
|
product: event.product,
|
||||||
name: event.product.name,
|
name: event.product.name!,
|
||||||
description: event.description,
|
description: event.description,
|
||||||
quantity: 1.0);
|
quantity: 1.0);
|
||||||
cartInfo.productList = [lineItem];
|
cartInfo.productList = [lineItem];
|
||||||
@@ -67,15 +76,15 @@ class Utils {
|
|||||||
eventBus.fire(new OnCartInfoUpdated());
|
eventBus.fire(new OnCartInfoUpdated());
|
||||||
print('#1');
|
print('#1');
|
||||||
} else {
|
} else {
|
||||||
if (event.product.productAttributes.length > 0) {
|
if (event.product.productAttributes!.length > 0) {
|
||||||
CartLineItem lineItem = newCartLineItem(
|
CartLineItem lineItem = newCartLineItem(
|
||||||
id: 0,
|
id: 0,
|
||||||
price: event.price,
|
price: event.price,
|
||||||
product: event.product,
|
product: event.product,
|
||||||
name: event.product.name,
|
name: event.product.name!,
|
||||||
description: event.description,
|
description: event.description,
|
||||||
quantity: 1.0);
|
quantity: 1.0);
|
||||||
cartInfo.productList.add(lineItem);
|
cartInfo.productList!.add(lineItem);
|
||||||
addSubproductToCard(cartInfo, lineItem);
|
addSubproductToCard(cartInfo, lineItem);
|
||||||
store.dispatch(new UpdateCartInfo(addCartInfoToCartInfoList(
|
store.dispatch(new UpdateCartInfo(addCartInfoToCartInfoList(
|
||||||
store.state.cartInfos, cartInfo)));
|
store.state.cartInfos, cartInfo)));
|
||||||
@@ -83,8 +92,8 @@ class Utils {
|
|||||||
eventBus.fire(new OnCartInfoUpdated());
|
eventBus.fire(new OnCartInfoUpdated());
|
||||||
} else {
|
} else {
|
||||||
int found = -1;
|
int found = -1;
|
||||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
for (var i = 0; i < cartInfo.productList!.length; i++) {
|
||||||
if (event.product.id == cartInfo.productList[i].product.id) {
|
if (event.product.id == cartInfo.productList![i].product!.id) {
|
||||||
found = i;
|
found = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -94,18 +103,18 @@ class Utils {
|
|||||||
id: 0,
|
id: 0,
|
||||||
price: event.price,
|
price: event.price,
|
||||||
product: event.product,
|
product: event.product,
|
||||||
name: event.product.name,
|
name: event.product.name!,
|
||||||
description: event.description,
|
description: event.description,
|
||||||
quantity: 1.0);
|
quantity: 1.0);
|
||||||
cartInfo.productList.add(lineItem);
|
cartInfo.productList!.add(lineItem);
|
||||||
addSubproductToCard(cartInfo, lineItem);
|
addSubproductToCard(cartInfo, lineItem);
|
||||||
store.dispatch(new UpdateCartInfo(addCartInfoToCartInfoList(
|
store.dispatch(new UpdateCartInfo(addCartInfoToCartInfoList(
|
||||||
store.state.cartInfos, cartInfo)));
|
store.state.cartInfos, cartInfo)));
|
||||||
print('#3');
|
print('#3');
|
||||||
eventBus.fire(new OnCartInfoUpdated());
|
eventBus.fire(new OnCartInfoUpdated());
|
||||||
} else {
|
} else {
|
||||||
if (cartInfo.productList[found].quantity + 1.0 >
|
if (cartInfo.productList![found].quantity! + 1.0 >
|
||||||
event.product.leftNum) {
|
event.product.leftNum!) {
|
||||||
// Fluttertoast.showToast(
|
// Fluttertoast.showToast(
|
||||||
// msg: S.of(context).product_insufficient,
|
// msg: S.of(context).product_insufficient,
|
||||||
// toastLength: Toast.LENGTH_SHORT,
|
// toastLength: Toast.LENGTH_SHORT,
|
||||||
@@ -113,8 +122,8 @@ class Utils {
|
|||||||
// backgroundColor: Colors.red,
|
// backgroundColor: Colors.red,
|
||||||
// textColor: Colors.white);
|
// textColor: Colors.white);
|
||||||
} else {
|
} else {
|
||||||
cartInfo.productList[found].quantity += 1;
|
cartInfo.productList![found].quantity = cartInfo.productList![found].quantity! + 1;
|
||||||
addSubproductQty(cartInfo, cartInfo.productList[found]);
|
addSubproductQty(cartInfo, cartInfo.productList![found]);
|
||||||
store.dispatch(new UpdateCartInfo(
|
store.dispatch(new UpdateCartInfo(
|
||||||
addCartInfoToCartInfoList(
|
addCartInfoToCartInfoList(
|
||||||
store.state.cartInfos, cartInfo)));
|
store.state.cartInfos, cartInfo)));
|
||||||
@@ -127,41 +136,41 @@ class Utils {
|
|||||||
});
|
});
|
||||||
onProductWillRemoveFromCartSubscription =
|
onProductWillRemoveFromCartSubscription =
|
||||||
eventBus.on<OnProductWillRemoveFromCart>().listen((event) {
|
eventBus.on<OnProductWillRemoveFromCart>().listen((event) {
|
||||||
CartInfo cartInfo =
|
CartInfo? cartInfo =
|
||||||
getCartInfoByBusiness(store.state.cartInfos, event.business);
|
getCartInfoByBusiness(store.state.cartInfos, event.business);
|
||||||
if (cartInfo != null) {
|
if (cartInfo != null) {
|
||||||
if (cartInfo.productList.length > 0) {
|
if (cartInfo.productList!.length > 0) {
|
||||||
if (event.productListIndex != -1) {
|
if (event.productListIndex != -1) {
|
||||||
if (cartInfo.productList[event.productListIndex].quantity <= 1) {
|
if (cartInfo.productList![event.productListIndex].quantity! <= 1) {
|
||||||
String uuid = cartInfo.productList[event.productListIndex].uuid;
|
String uuid = cartInfo.productList![event.productListIndex].uuid!;
|
||||||
cartInfo.productList.removeAt(event.productListIndex);
|
cartInfo.productList!.removeAt(event.productListIndex);
|
||||||
removeSubproduct(cartInfo, uuid);
|
removeSubproduct(cartInfo, uuid);
|
||||||
} else {
|
} else {
|
||||||
cartInfo.productList[event.productListIndex].quantity -= 1;
|
cartInfo.productList![event.productListIndex].quantity = cartInfo.productList![event.productListIndex].quantity! - 1;
|
||||||
addSubproductQty(cartInfo, cartInfo.productList[event.productListIndex], remove: true);
|
addSubproductQty(cartInfo, cartInfo.productList![event.productListIndex], remove: true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int productListIndex = -1;
|
int productListIndex = -1;
|
||||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
for (var i = 0; i < cartInfo.productList!.length; i++) {
|
||||||
if (cartInfo.productList[i].product.id == event.product.id) {
|
if (cartInfo.productList![i].product!.id == event.product.id) {
|
||||||
productListIndex = i;
|
productListIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (productListIndex != -1) {
|
if (productListIndex != -1) {
|
||||||
if (cartInfo.productList[productListIndex].quantity <= 1) {
|
if (cartInfo.productList![productListIndex].quantity! <= 1) {
|
||||||
String uuid = cartInfo.productList[productListIndex].uuid;
|
String uuid = cartInfo.productList![productListIndex].uuid!;
|
||||||
cartInfo.productList.removeAt(productListIndex);
|
cartInfo.productList!.removeAt(productListIndex);
|
||||||
removeSubproduct(cartInfo, uuid);
|
removeSubproduct(cartInfo, uuid);
|
||||||
} else {
|
} else {
|
||||||
cartInfo.productList[productListIndex].quantity -= 1;
|
cartInfo.productList![productListIndex].quantity = cartInfo.productList![productListIndex].quantity! - 1;
|
||||||
addSubproductQty(cartInfo, cartInfo.productList[productListIndex], remove: true);
|
addSubproductQty(cartInfo, cartInfo.productList![productListIndex], remove: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cartInfo.productList.length <= 0) {
|
if (cartInfo.productList!.length <= 0) {
|
||||||
store.dispatch(UpdateCartInfo(removeCartInfoFromCartInfoList(
|
store.dispatch(UpdateCartInfo(removeCartInfoFromCartInfoList(
|
||||||
store.state.cartInfos, cartInfo)));
|
store.state.cartInfos, cartInfo)));
|
||||||
} else {
|
} else {
|
||||||
@@ -211,7 +220,7 @@ class Utils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, dynamic> stringToJson(String string) {
|
static Map<String, dynamic>? stringToJson(String? string) {
|
||||||
if (string == null || string.isEmpty) {
|
if (string == null || string.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -244,10 +253,6 @@ class Utils {
|
|||||||
return platformName;
|
return platformName;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Box> getBox() async {
|
|
||||||
return Util.getBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Widget imageLoadingIndicator(
|
static Widget imageLoadingIndicator(
|
||||||
{double width = 30.0, double height = 30.0}) {
|
{double width = 30.0, double height = 30.0}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -365,7 +370,7 @@ class Utils {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static showLoadingDialog(BuildContext context, {String message}) {
|
static showLoadingDialog(BuildContext context, {String? message}) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
@@ -468,46 +473,45 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void getCurrentUser() {
|
static void getCurrentUser() {
|
||||||
getBox().then((box) {
|
if (store.state.user != null) {
|
||||||
int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0);
|
eventBus.fire(OnCurrentUserUpdated());
|
||||||
String accessToken =
|
return;
|
||||||
box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
}
|
||||||
if (userId == 0 || accessToken.isEmpty) {
|
int? userId = prefs?.getInt(Constants.KEY_USER_ID) ?? 0;
|
||||||
eventBus.fire(new OnGetCurrentUserFailed(Error()));
|
String? accessToken = prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
|
||||||
} else {
|
if (userId == 0 || accessToken.isEmpty) {
|
||||||
HttpUtil.httpGet(
|
eventBus.fire(new OnGetCurrentUserFailed(Error()));
|
||||||
'v1/users/$userId',
|
} else {
|
||||||
queryParameters: {},
|
HttpUtil.httpGet(
|
||||||
businessId: Constants.BUSINESS_ID,
|
'v1/users/$userId',
|
||||||
).then((value) {
|
queryParameters: {},
|
||||||
User user = User.fromJson(value);
|
businessId: Constants.BUSINESS_ID,
|
||||||
store.dispatch(UpdateCurrentUser(user));
|
).then((value) {
|
||||||
eventBus.fire(OnCurrentUserUpdated());
|
User user = User.fromJson(value);
|
||||||
}).catchError((err) {
|
store.dispatch(UpdateCurrentUser(user));
|
||||||
eventBus.fire(new OnGetCurrentUserFailed(err));
|
eventBus.fire(OnCurrentUserUpdated());
|
||||||
});
|
}).catchError((err) {
|
||||||
}
|
eventBus.fire(new OnGetCurrentUserFailed(err));
|
||||||
}).catchError((err) {
|
});
|
||||||
eventBus.fire(new OnGetCurrentUserFailed(err));
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void showMessageDialog(BuildContext context, dynamic error,
|
static void showMessageDialog(BuildContext context, dynamic error,
|
||||||
{String title, List<Widget> actions, OnOk onOk}) {
|
{String? title, List<Widget>? actions, OnOk? onOk}) {
|
||||||
String message = '';
|
String message = '';
|
||||||
print('error $error');
|
print('error $error');
|
||||||
if (error is DioError && error.response != null) {
|
if (error is DioException && error.response != null) {
|
||||||
if (error.response.data['message'] != null) {
|
if (error.response!.data['message'] != null) {
|
||||||
message = error.response.data['message'];
|
message = error.response!.data['message'];
|
||||||
} else if (error.response.data['error'] != null) {
|
} else if (error.response!.data['error'] != null) {
|
||||||
if (error.response.data['error'] is String) {
|
if (error.response!.data['error'] is String) {
|
||||||
message = error.response.data['error'];
|
message = error.response!.data['error'];
|
||||||
} else if (error.response.data['error']['errmsg'] != null) {
|
} else if (error.response!.data['error']['errmsg'] != null) {
|
||||||
message = error.response.data['error']['errmsg'];
|
message = error.response!.data['error']['errmsg'];
|
||||||
} else if (error.response.data['error']['message'] != null) {
|
} else if (error.response!.data['error']['message'] != null) {
|
||||||
message = error.response.data['error']['message'];
|
message = error.response!.data['error']['message'];
|
||||||
} else {
|
} else {
|
||||||
message = error.response.data.toString();
|
message = error.response!.data.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (error is String) {
|
} else if (error is String) {
|
||||||
@@ -540,20 +544,15 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<int>> getLastVisit() async {
|
static Future<List<int>> getLastVisit() async {
|
||||||
Box box = await getBox();
|
|
||||||
List<dynamic> lastVisit =
|
List<dynamic> lastVisit =
|
||||||
json.decode(box.get(Constants.KEY_LAST_VISIT, defaultValue: '[]'));
|
json.decode(prefs?.getString(Constants.KEY_LAST_VISIT) ?? '[]');
|
||||||
List<int> lv = lastVisit.map((e) => e as int).toList();
|
List<int> lv = lastVisit.map((e) => e as int).toList();
|
||||||
store.dispatch(UpdateLastVisit(lv));
|
store.dispatch(UpdateLastVisit(lv));
|
||||||
return lv;
|
return lv;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void saveLastVisit(List<int> lastVisit) {
|
static void saveLastVisit(List<int> lastVisit) {
|
||||||
getBox().then((box) {
|
prefs?.setString(Constants.KEY_LAST_VISIT, lastVisit.toString());
|
||||||
box.put(Constants.KEY_LAST_VISIT, lastVisit.toString());
|
|
||||||
}).catchError((error) {
|
|
||||||
print('Error occurred: $error');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String utcDatetimeStringToLocalDatetimeString(
|
static String utcDatetimeStringToLocalDatetimeString(
|
||||||
@@ -656,26 +655,26 @@ class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CartInfo getCartInfoByBusiness(
|
static CartInfo? getCartInfoByBusiness(
|
||||||
List<CartInfo> cartInfos, Business business) {
|
List<CartInfo>? cartInfos, Business business) {
|
||||||
if (cartInfos == null || cartInfos.length <= 0) {
|
if (cartInfos == null || cartInfos.length <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < cartInfos.length; i++) {
|
for (var i = 0; i < cartInfos.length; i++) {
|
||||||
if (cartInfos[i].businessInfo.id == business.id) {
|
if (cartInfos[i].businessInfo!.id == business.id) {
|
||||||
return cartInfos[i];
|
return cartInfos[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CartInfo getCartInfoByBusinessId(
|
static CartInfo? getCartInfoByBusinessId(
|
||||||
List<CartInfo> cartInfos, int businessId) {
|
List<CartInfo>? cartInfos, int businessId) {
|
||||||
if (cartInfos == null || cartInfos.length <= 0) {
|
if (cartInfos == null || cartInfos.length <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < cartInfos.length; i++) {
|
for (var i = 0; i < cartInfos.length; i++) {
|
||||||
if (cartInfos[i].businessInfo.id == businessId) {
|
if (cartInfos[i].businessInfo!.id == businessId) {
|
||||||
return cartInfos[i];
|
return cartInfos[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -683,13 +682,13 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static List<CartInfo> addCartInfoToCartInfoList(
|
static List<CartInfo> addCartInfoToCartInfoList(
|
||||||
List<CartInfo> cartInfos, CartInfo cartInfo) {
|
List<CartInfo>? cartInfos, CartInfo cartInfo) {
|
||||||
if (cartInfos == null || cartInfos.length <= 0) {
|
if (cartInfos == null || cartInfos.length <= 0) {
|
||||||
cartInfos = [cartInfo];
|
cartInfos = [cartInfo];
|
||||||
} else {
|
} else {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (var i = 0; i < cartInfos.length; i++) {
|
for (var i = 0; i < cartInfos.length; i++) {
|
||||||
if (cartInfos[i].businessInfo.id == cartInfo.businessInfo.id) {
|
if (cartInfos[i].businessInfo!.id == cartInfo.businessInfo!.id) {
|
||||||
cartInfos[i] = cartInfo;
|
cartInfos[i] = cartInfo;
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
@@ -699,22 +698,19 @@ class Utils {
|
|||||||
cartInfos.add(cartInfo);
|
cartInfos.add(cartInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Utils.getBox().then((box) {
|
prefs?.setString(Constants.KEY_CARTINFOS, cartInfos.toString());
|
||||||
box.put(Constants.KEY_CARTINFOS, cartInfos.toString());
|
|
||||||
}).catchError((error) {
|
|
||||||
print('Error occurred: $error');
|
|
||||||
});
|
|
||||||
return cartInfos;
|
return cartInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<CartInfo> removeCartInfoFromCartInfoList(
|
static List<CartInfo> removeCartInfoFromCartInfoList(
|
||||||
List<CartInfo> cartInfos, CartInfo cartInfo) {
|
List<CartInfo>? cartInfos, CartInfo? cartInfo) {
|
||||||
if (cartInfos == null || cartInfos.length <= 0 || cartInfo == null) {
|
if (cartInfos == null || cartInfos.length <= 0 || cartInfo == null) {
|
||||||
|
prefs?.setString(Constants.KEY_CARTINFOS, cartInfos.toString());
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
int removeIndex = -1;
|
int removeIndex = -1;
|
||||||
for (var i = 0; i < cartInfos.length; i++) {
|
for (var i = 0; i < cartInfos.length; i++) {
|
||||||
if (cartInfo.businessInfo.id == cartInfos[i].businessInfo.id) {
|
if (cartInfo.businessInfo!.id == cartInfos[i].businessInfo!.id) {
|
||||||
removeIndex = i;
|
removeIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -722,17 +718,13 @@ class Utils {
|
|||||||
if (removeIndex != -1) {
|
if (removeIndex != -1) {
|
||||||
cartInfos.removeAt(removeIndex);
|
cartInfos.removeAt(removeIndex);
|
||||||
}
|
}
|
||||||
getBox().then((box) {
|
prefs?.setString(Constants.KEY_CARTINFOS, cartInfos.toString());
|
||||||
box.put(Constants.KEY_CARTINFOS, cartInfos.toString());
|
|
||||||
}).catchError((error) {
|
|
||||||
print('Error occurred: $error');
|
|
||||||
});
|
|
||||||
return cartInfos;
|
return cartInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clearCart(BuildContext context, CartInfo cartInfo,
|
static void clearCart(BuildContext context, CartInfo cartInfo,
|
||||||
{OnComplete onComplete}) {
|
{OnComplete? onComplete}) {
|
||||||
if (cartInfo != null && cartInfo.productList.length > 0) {
|
if (cartInfo != null && cartInfo.productList!.length > 0) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
@@ -766,7 +758,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void requireLogin(BuildContext context,
|
static void requireLogin(BuildContext context,
|
||||||
{String returnUrl, bool replace = true, int delayMilliseconds = 400}) {
|
{String? returnUrl, bool replace = true, int delayMilliseconds = 400}) {
|
||||||
Future.delayed(Duration(milliseconds: delayMilliseconds), () {
|
Future.delayed(Duration(milliseconds: delayMilliseconds), () {
|
||||||
if (returnUrl != null) {
|
if (returnUrl != null) {
|
||||||
store.dispatch(UpdateRedirectRoute(returnUrl));
|
store.dispatch(UpdateRedirectRoute(returnUrl));
|
||||||
@@ -775,9 +767,9 @@ class Utils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getFirstNumberFromString(String numString) {
|
static String? getFirstNumberFromString(String numString) {
|
||||||
final intInStr = RegExp(r'\d+');
|
final intInStr = RegExp(r'\d+');
|
||||||
return intInStr.firstMatch(numString).group(0);
|
return intInStr.firstMatch(numString)?.group(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadProducts(int businessId, int categoryId, int page, bool more,
|
static void loadProducts(int businessId, int categoryId, int page, bool more,
|
||||||
@@ -845,7 +837,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getWeekDayName(BuildContext context, int timestamp, bool shortName) {
|
static String? getWeekDayName(BuildContext context, int timestamp, bool shortName) {
|
||||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||||
switch(date.weekday) {
|
switch(date.weekday) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -854,61 +846,54 @@ class Utils {
|
|||||||
} else {
|
} else {
|
||||||
return S.of(context).monday;
|
return S.of(context).monday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
return S.of(context).tue;
|
return S.of(context).tue;
|
||||||
} else {
|
} else {
|
||||||
return S.of(context).tuesday;
|
return S.of(context).tuesday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 3:
|
case 3:
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
return S.of(context).wed;
|
return S.of(context).wed;
|
||||||
} else {
|
} else {
|
||||||
return S.of(context).wednesday;
|
return S.of(context).wednesday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 4:
|
case 4:
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
return S.of(context).thu;
|
return S.of(context).thu;
|
||||||
} else {
|
} else {
|
||||||
return S.of(context).thursday;
|
return S.of(context).thursday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 5:
|
case 5:
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
return S.of(context).fri;
|
return S.of(context).fri;
|
||||||
} else {
|
} else {
|
||||||
return S.of(context).friday;
|
return S.of(context).friday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 6:
|
case 6:
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
return S.of(context).sat;
|
return S.of(context).sat;
|
||||||
} else {
|
} else {
|
||||||
return S.of(context).saturday;
|
return S.of(context).saturday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 7:
|
case 7:
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
return S.of(context).sun;
|
return S.of(context).sun;
|
||||||
} else {
|
} else {
|
||||||
return S.of(context).sunday;
|
return S.of(context).sunday;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stripePaymentIntent(Order order, String customerId, String paymentMethodId, String cardType,
|
static void stripePaymentIntent(Order order, String? customerId, String paymentMethodId, String cardType,
|
||||||
OnSuccess onSuccess, OnError onError,
|
OnSuccess onSuccess, OnError onError,
|
||||||
{
|
{
|
||||||
String cardBrand,
|
String? cardBrand,
|
||||||
String cardCountry,
|
String? cardCountry,
|
||||||
int cardExpMonth,
|
int? cardExpMonth,
|
||||||
int cardExpYear,
|
int? cardExpYear,
|
||||||
String cardFunding,
|
String? cardFunding,
|
||||||
String cardLast4,
|
String? cardLast4,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
HttpUtil.httpPost('v1/stripe-payment-intent',
|
HttpUtil.httpPost('v1/stripe-payment-intent',
|
||||||
@@ -919,12 +904,12 @@ class Utils {
|
|||||||
'customer_id': customerId == null ? '' : customerId,
|
'customer_id': customerId == null ? '' : customerId,
|
||||||
'payment_method_id': paymentMethodId,
|
'payment_method_id': paymentMethodId,
|
||||||
'payment_method_type': cardType,
|
'payment_method_type': cardType,
|
||||||
'card_brand': cardBrand,
|
'card_brand': cardBrand ?? '',
|
||||||
'card_country': cardCountry,
|
'card_country': cardCountry ?? '',
|
||||||
'card_exp_month': cardExpMonth,
|
'card_exp_month': cardExpMonth ?? 0,
|
||||||
'card_exp_year': cardExpYear,
|
'card_exp_year': cardExpYear ?? 0,
|
||||||
'card_funding': cardFunding,
|
'card_funding': cardFunding ?? '',
|
||||||
'card_last4': cardLast4,
|
'card_last4': cardLast4 ?? '',
|
||||||
},
|
},
|
||||||
isFormData: true,
|
isFormData: true,
|
||||||
).catchError(onError);
|
).catchError(onError);
|
||||||
@@ -945,8 +930,8 @@ class Utils {
|
|||||||
|
|
||||||
static int getProductLineInOrder(CartInfo cartInfo) {
|
static int getProductLineInOrder(CartInfo cartInfo) {
|
||||||
int qty = 0;
|
int qty = 0;
|
||||||
for (CartLineItem lineItem in cartInfo.productList) {
|
for (CartLineItem lineItem in cartInfo.productList!) {
|
||||||
if (lineItem.product.id != null) {
|
if (lineItem.product?.id != null) {
|
||||||
qty += 1;
|
qty += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -954,10 +939,10 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void orderAgain(BuildContext context, CartInfo cartInfo) {
|
static void orderAgain(BuildContext context, CartInfo cartInfo) {
|
||||||
cartInfo.productList.removeWhere((element) => element.product == null || element.product.id == null);
|
cartInfo.productList!.removeWhere((element) => element.product == null || element.product!.id == null);
|
||||||
store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
|
store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
|
||||||
eventBus.fire(new OnCartInfoUpdated());
|
eventBus.fire(new OnCartInfoUpdated());
|
||||||
Routes.router.navigateTo(context, '/checkout/${cartInfo.businessInfo.id}');
|
Routes.router.navigateTo(context, '/checkout/${cartInfo.businessInfo!.id}');
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getOrderStatus(BuildContext context, int statusCode) {
|
static String getOrderStatus(BuildContext context, int statusCode) {
|
||||||
@@ -978,42 +963,42 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static CartLineItem newCartLineItem(
|
static CartLineItem newCartLineItem(
|
||||||
{int id,
|
{int? id,
|
||||||
double price,
|
double? price,
|
||||||
Product product,
|
Product? product,
|
||||||
String name,
|
String? name,
|
||||||
String description,
|
String? description,
|
||||||
double quantity,
|
double? quantity,
|
||||||
String parentUuid,
|
String? parentUuid,
|
||||||
}) {
|
}) {
|
||||||
CartLineItem lineItem = CartLineItem();
|
CartLineItem lineItem = CartLineItem();
|
||||||
lineItem.unitPrice = price;
|
lineItem.unitPrice = price;
|
||||||
lineItem.product = product;
|
lineItem.product = product;
|
||||||
lineItem.name = product.name;
|
lineItem.name = product?.name ?? '';
|
||||||
lineItem.description = description;
|
lineItem.description = description;
|
||||||
lineItem.quantity = quantity;
|
lineItem.quantity = quantity;
|
||||||
lineItem.parentUuid = parentUuid;
|
lineItem.parentUuid = parentUuid ?? '';
|
||||||
return lineItem;
|
return lineItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addSubproductToCard(CartInfo cartInfo, CartLineItem lineItem) {
|
static void addSubproductToCard(CartInfo cartInfo, CartLineItem lineItem) {
|
||||||
if (lineItem.product.subproducts.length > 0) {
|
if (lineItem.product!.subproducts!.length > 0) {
|
||||||
for (int j = 0; j < lineItem.product.subproducts.length; j++) {
|
for (int j = 0; j < lineItem.product!.subproducts!.length; j++) {
|
||||||
cartInfo.productList.add(
|
cartInfo.productList!.add(
|
||||||
newCartLineItem(
|
newCartLineItem(
|
||||||
id: 0,
|
id: 0,
|
||||||
price: 0.0,
|
price: 0.0,
|
||||||
product: Product(
|
product: Product(
|
||||||
lineItem.product.subproducts[j].product.id,
|
lineItem.product!.subproducts![j].product.id,
|
||||||
lineItem.product.businessId,
|
lineItem.product!.businessId!,
|
||||||
lineItem.product.subproducts[j].product.name,
|
lineItem.product!.subproducts![j].product.name,
|
||||||
lineItem.product.subproducts[j].product.price,
|
lineItem.product!.subproducts![j].product.price,
|
||||||
lineItem.product.subproducts[j].product.description,
|
lineItem.product!.subproducts![j].product.description,
|
||||||
lineItem.product.subproducts[j].product.image,
|
lineItem.product!.subproducts![j].product.image,
|
||||||
lineItem.product.subproducts[j].product.productAttributes
|
lineItem.product!.subproducts![j].product.productAttributes
|
||||||
),
|
),
|
||||||
name: lineItem.product.subproducts[j].product.name,
|
name: lineItem.product!.subproducts![j].product.name,
|
||||||
description: lineItem.product.subproducts[j].product.description,
|
description: lineItem.product!.subproducts![j].product.description,
|
||||||
quantity: 1.0,
|
quantity: 1.0,
|
||||||
parentUuid: lineItem.uuid
|
parentUuid: lineItem.uuid
|
||||||
)
|
)
|
||||||
@@ -1024,16 +1009,16 @@ class Utils {
|
|||||||
|
|
||||||
static void addSubproductQty(CartInfo cartInfo, CartLineItem lineItem,
|
static void addSubproductQty(CartInfo cartInfo, CartLineItem lineItem,
|
||||||
{bool remove = false}) {
|
{bool remove = false}) {
|
||||||
if (lineItem.product.subproducts.length > 0) {
|
if (lineItem.product!.subproducts!.length > 0) {
|
||||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
for (var i = 0; i < cartInfo.productList!.length; i++) {
|
||||||
CartLineItem lineItem1 = cartInfo.productList[i];
|
CartLineItem lineItem1 = cartInfo.productList![i];
|
||||||
if (lineItem1.parentUuid == lineItem.uuid) {
|
if (lineItem1.parentUuid == lineItem.uuid) {
|
||||||
for (var j = 0; j < lineItem.product.subproducts.length; j++) {
|
for (var j = 0; j < lineItem.product!.subproducts!.length; j++) {
|
||||||
if (lineItem.product.subproducts[j].product.id == lineItem1.product.id) {
|
if (lineItem.product!.subproducts![j].product.id == lineItem1.product!.id) {
|
||||||
if (remove) {
|
if (remove) {
|
||||||
lineItem1.quantity -= lineItem.product.subproducts[j].quantity;
|
lineItem1.quantity = lineItem1.quantity! - lineItem.product!.subproducts![j].quantity;
|
||||||
} else {
|
} else {
|
||||||
lineItem1.quantity += lineItem.product.subproducts[j].quantity;
|
lineItem1.quantity = lineItem1.quantity! + lineItem.product!.subproducts![j].quantity;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1044,7 +1029,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void removeSubproduct(CartInfo cartInfo, String uuid) {
|
static void removeSubproduct(CartInfo cartInfo, String uuid) {
|
||||||
cartInfo.productList.removeWhere((element) => element.parentUuid == uuid);
|
cartInfo.productList!.removeWhere((element) => element.parentUuid == uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void openEmail(String email) async {
|
static void openEmail(String email) async {
|
||||||
@@ -1069,7 +1054,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Widget buildLine(String name, dynamic value, {double nameSize, double valueSize}) {
|
static Widget buildLine(String name, dynamic value, {double? nameSize, double? valueSize}) {
|
||||||
Row row = Row(
|
Row row = Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1119,7 +1104,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RuntimeError extends Error {
|
class RuntimeError extends Error {
|
||||||
final int code;
|
final int? code;
|
||||||
final String message;
|
final String message;
|
||||||
RuntimeError(this.message, {this.code});
|
RuntimeError(this.message, {this.code});
|
||||||
String toString() => "Runtime Error: $message";
|
String toString() => "Runtime Error: $message";
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright (c) 2015, Benjamin NGUYEN. All rights reserved. Use of this source code
|
||||||
|
// is governed by a BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/// The countdown library.
|
||||||
|
library countdown;
|
||||||
|
|
||||||
|
export 'src/countdown_base.dart';
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright (c) 2015, Benjamin NGUYEN. All rights reserved. Use of this source code
|
||||||
|
// is governed by a BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
library countdown.base;
|
||||||
|
|
||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
class CountDown {
|
||||||
|
/// reference point for start and resume
|
||||||
|
DateTime? _begin;
|
||||||
|
Timer? _timer;
|
||||||
|
Duration? _duration;
|
||||||
|
Duration? remainingTime;
|
||||||
|
bool isPaused = false;
|
||||||
|
StreamController<Duration>? _controller;
|
||||||
|
Duration? _refresh;
|
||||||
|
|
||||||
|
/// provide a way to send less data to the client but keep the data of the timer up to date
|
||||||
|
int _everyTick = 0;
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
/// once you instantiate the CountDown you need to register to receive information
|
||||||
|
CountDown(Duration duration,
|
||||||
|
{Duration refresh = const Duration(milliseconds: 10), int everyTick = 1}) {
|
||||||
|
_refresh = refresh;
|
||||||
|
_everyTick = everyTick;
|
||||||
|
|
||||||
|
this._duration = duration;
|
||||||
|
_controller = StreamController<Duration>(
|
||||||
|
onListen: _onListen,
|
||||||
|
onPause: _onPause,
|
||||||
|
onResume: _onResume,
|
||||||
|
onCancel: _onCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Duration> get stream => _controller!.stream;
|
||||||
|
|
||||||
|
/// _onListen
|
||||||
|
/// invoke when the first subscriber has subscribe and not before to avoid leak of memory
|
||||||
|
_onListen() {
|
||||||
|
// reference point
|
||||||
|
_begin = DateTime.now();
|
||||||
|
_timer = Timer.periodic(_refresh!, _tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the remaining time is set at '_refresh' ms accurate
|
||||||
|
_onPause() {
|
||||||
|
isPaused = true;
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ...restart the timer with the duration
|
||||||
|
_onResume() {
|
||||||
|
_begin = DateTime.now();
|
||||||
|
|
||||||
|
_duration = this.remainingTime;
|
||||||
|
isPaused = false;
|
||||||
|
|
||||||
|
// lance le timer
|
||||||
|
_timer = Timer.periodic(_refresh!, _tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onCancel() {
|
||||||
|
// on pause we already cancel the _timer
|
||||||
|
if (!isPaused) {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
// _controller.close(); // close automatically the "pipe" when the sub close it by sub.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
void _tick(Timer? timer) {
|
||||||
|
counter++;
|
||||||
|
Duration alreadyConsumed = DateTime.now().difference(_begin!);
|
||||||
|
this.remainingTime = this._duration! - alreadyConsumed;
|
||||||
|
if (this.remainingTime!.isNegative) {
|
||||||
|
timer?.cancel();
|
||||||
|
timer = null;
|
||||||
|
// tell the onDone's subscriber that it's finish
|
||||||
|
_controller?.close();
|
||||||
|
} else {
|
||||||
|
// here we can control the frequency of sending data
|
||||||
|
if (counter % _everyTick == 0) {
|
||||||
|
_controller?.add(this.remainingTime!);
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Allows the user to close the app by double tapping the back-button.
|
||||||
|
///
|
||||||
|
/// You must specify a [SnackBar], so it can be shown when the user taps the
|
||||||
|
/// back-button. Notice that the value you set for [SnackBar.duration] is going
|
||||||
|
/// to be considered to decide whether the snack-bar is currently visible or
|
||||||
|
/// not.
|
||||||
|
///
|
||||||
|
/// Since the back-button is an Android feature, this Widget is going to be
|
||||||
|
/// nothing but the own [child] if the current platform is anything but Android.
|
||||||
|
class DoubleBackToCloseApp extends StatefulWidget {
|
||||||
|
/// The [SnackBar] shown when the user taps the back-button.
|
||||||
|
final SnackBar snackBar;
|
||||||
|
|
||||||
|
/// The widget below this widget in the tree.
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// Creates a widget that allows the user to close the app by double tapping
|
||||||
|
/// the back-button.
|
||||||
|
const DoubleBackToCloseApp({
|
||||||
|
Key? key,
|
||||||
|
required this.snackBar,
|
||||||
|
required this.child,
|
||||||
|
}) : assert(snackBar != null),
|
||||||
|
assert(child != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DoubleBackToCloseAppState createState() => _DoubleBackToCloseAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
|
||||||
|
/// The last time the user tapped Android's back-button.
|
||||||
|
DateTime? _lastTimeBackButtonWasTapped;
|
||||||
|
|
||||||
|
/// Returns whether the current platform is Android.
|
||||||
|
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
|
||||||
|
|
||||||
|
/// Returns whether the [DoubleBackToCloseApp.snackBar] is currently visible.
|
||||||
|
///
|
||||||
|
/// The snack-bar is going to be considered visible if the duration of the
|
||||||
|
/// snack-bar is greater than the difference from now to the
|
||||||
|
/// [_lastTimeBackButtonWasTapped].
|
||||||
|
///
|
||||||
|
/// This is not quite accurate since the snack-bar could've been dismissed by
|
||||||
|
/// the user, so this algorithm needs to be improved, as described in #2.
|
||||||
|
bool get _isSnackBarVisible =>
|
||||||
|
(_lastTimeBackButtonWasTapped != null) &&
|
||||||
|
(widget.snackBar.duration >
|
||||||
|
DateTime.now().difference(_lastTimeBackButtonWasTapped!));
|
||||||
|
|
||||||
|
/// Returns whether the next back navigation of this route will be handled
|
||||||
|
/// internally.
|
||||||
|
///
|
||||||
|
/// Returns true when there's a widget that inserted an entry into the
|
||||||
|
/// local-history of the current route, in order to handle pop. This is done
|
||||||
|
/// by [Drawer], for example, so it can close on pop.
|
||||||
|
bool get _willHandlePopInternally =>
|
||||||
|
ModalRoute.of(context)!.willHandlePopInternally;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_ensureThatContextContainsScaffold();
|
||||||
|
|
||||||
|
if (_isAndroid) {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: _handleWillPop,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles [WillPopScope.onWillPop].
|
||||||
|
Future<bool> _handleWillPop() async {
|
||||||
|
if (_isSnackBarVisible || _willHandlePopInternally) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
_lastTimeBackButtonWasTapped = DateTime.now();
|
||||||
|
// Scaffold.of(context).showSnackBar(widget.snackBar);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(widget.snackBar);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Throws a [FlutterError] if this widget was not wrapped in a [Scaffold].
|
||||||
|
void _ensureThatContextContainsScaffold() {
|
||||||
|
if (Scaffold.maybeOf(context) == null) {
|
||||||
|
throw FlutterError(
|
||||||
|
'`DoubleBackToCloseApp` must be wrapped in a `Scaffold`.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+449
@@ -0,0 +1,449 @@
|
|||||||
|
library flappy_search_bar;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:async/async.dart';
|
||||||
|
import 'scaled_tile.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
|
|
||||||
|
import 'search_bar_style.dart';
|
||||||
|
|
||||||
|
mixin _ControllerListener<T> on State<SearchBar<T>> {
|
||||||
|
void onListChanged(List<T> items) {}
|
||||||
|
|
||||||
|
void onLoading() {}
|
||||||
|
|
||||||
|
void onClear() {}
|
||||||
|
|
||||||
|
void onError(Error error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchBarController<T> {
|
||||||
|
final List<T> _list = [];
|
||||||
|
final List<T> _filteredList = [];
|
||||||
|
final List<T> _sortedList = [];
|
||||||
|
TextEditingController? _searchQueryController;
|
||||||
|
String? _lastSearchedText;
|
||||||
|
Future<List<T>> Function(String text)? _lastSearchFunction;
|
||||||
|
_ControllerListener? _controllerListener;
|
||||||
|
int Function(T a, T b)? _lastSorting;
|
||||||
|
CancelableOperation? _cancelableOperation;
|
||||||
|
int? minimumChars;
|
||||||
|
|
||||||
|
void setTextController(TextEditingController _searchQueryController, minimunChars) {
|
||||||
|
this._searchQueryController = _searchQueryController;
|
||||||
|
this.minimumChars = minimunChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setListener(_ControllerListener _controllerListener) {
|
||||||
|
this._controllerListener = _controllerListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
_controllerListener?.onClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _search(
|
||||||
|
String text, Future<List<T>> Function(String text) onSearch) async {
|
||||||
|
_controllerListener?.onLoading();
|
||||||
|
try {
|
||||||
|
if (_cancelableOperation != null &&
|
||||||
|
(!_cancelableOperation!.isCompleted ||
|
||||||
|
!_cancelableOperation!.isCanceled)) {
|
||||||
|
_cancelableOperation!.cancel();
|
||||||
|
}
|
||||||
|
_cancelableOperation = CancelableOperation.fromFuture(
|
||||||
|
onSearch(text),
|
||||||
|
onCancel: () => {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<T> items = await _cancelableOperation!.value;
|
||||||
|
_lastSearchFunction = onSearch;
|
||||||
|
_lastSearchedText = text;
|
||||||
|
_list.clear();
|
||||||
|
_filteredList.clear();
|
||||||
|
_sortedList.clear();
|
||||||
|
_lastSorting = null;
|
||||||
|
_list.addAll(items);
|
||||||
|
_controllerListener?.onListChanged(_list);
|
||||||
|
} catch (error) {
|
||||||
|
_controllerListener?.onError(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void injectSearch(
|
||||||
|
String searchText, Future<List<T>> Function(String text) onSearch) {
|
||||||
|
if (searchText != null && searchText.length >= minimumChars!) {
|
||||||
|
_searchQueryController?.text = searchText;
|
||||||
|
_search(searchText, onSearch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void replayLastSearch() {
|
||||||
|
if (_lastSearchFunction != null && _lastSearchedText != null) {
|
||||||
|
_search(_lastSearchedText!, _lastSearchFunction!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFilter() {
|
||||||
|
_filteredList.clear();
|
||||||
|
if (_lastSorting == null) {
|
||||||
|
_controllerListener?.onListChanged(_list);
|
||||||
|
} else {
|
||||||
|
_sortedList.clear();
|
||||||
|
_sortedList.addAll(List<T>.from(_list));
|
||||||
|
_sortedList.sort(_lastSorting);
|
||||||
|
_controllerListener?.onListChanged(_sortedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSort() {
|
||||||
|
_sortedList.clear();
|
||||||
|
_lastSorting = null;
|
||||||
|
_controllerListener
|
||||||
|
?.onListChanged(_filteredList.isEmpty ? _list : _filteredList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sortList(int Function(T a, T b) sorting) {
|
||||||
|
_lastSorting = sorting;
|
||||||
|
_sortedList.clear();
|
||||||
|
_sortedList
|
||||||
|
.addAll(List<T>.from(_filteredList.isEmpty ? _list : _filteredList));
|
||||||
|
_sortedList.sort(sorting);
|
||||||
|
_controllerListener?.onListChanged(_sortedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterList(bool Function(T item) filter) {
|
||||||
|
_filteredList.clear();
|
||||||
|
_filteredList.addAll(_sortedList.isEmpty
|
||||||
|
? _list.where(filter).toList()
|
||||||
|
: _sortedList.where(filter).toList());
|
||||||
|
_controllerListener?.onListChanged(_filteredList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signature for a function that creates [ScaledTile] for a given index.
|
||||||
|
typedef ScaledTile IndexedScaledTileBuilder(int index);
|
||||||
|
|
||||||
|
class SearchBar<T> extends StatefulWidget {
|
||||||
|
/// Future returning searched items
|
||||||
|
final Future<List<T>> Function(String text) onSearch;
|
||||||
|
|
||||||
|
/// List of items showed by default
|
||||||
|
final List<T> suggestions;
|
||||||
|
|
||||||
|
/// Callback returning the widget corresponding to a Suggestion item
|
||||||
|
final Widget Function(T item, int index)? buildSuggestion;
|
||||||
|
|
||||||
|
/// Minimum number of chars required for a search
|
||||||
|
final int minimumChars;
|
||||||
|
|
||||||
|
/// Callback returning the widget corresponding to an item found
|
||||||
|
final Widget Function(T item, int index) onItemFound;
|
||||||
|
|
||||||
|
/// Callback returning the widget corresponding to an Error while searching
|
||||||
|
final Widget Function(Error error)? onError;
|
||||||
|
|
||||||
|
/// Cooldown between each call to avoid too many
|
||||||
|
final Duration debounceDuration;
|
||||||
|
|
||||||
|
/// Widget to show when loading
|
||||||
|
final Widget loader;
|
||||||
|
|
||||||
|
/// Widget to show when no item were found
|
||||||
|
final Widget emptyWidget;
|
||||||
|
|
||||||
|
/// Widget to show by default
|
||||||
|
final Widget? placeHolder;
|
||||||
|
|
||||||
|
/// Widget showed on left of the search bar
|
||||||
|
final Widget icon;
|
||||||
|
|
||||||
|
/// Widget placed between the search bar and the results
|
||||||
|
final Widget? header;
|
||||||
|
|
||||||
|
/// Hint text of the search bar
|
||||||
|
final String hintText;
|
||||||
|
|
||||||
|
/// TextStyle of the hint text
|
||||||
|
final TextStyle hintStyle;
|
||||||
|
|
||||||
|
/// Color of the icon when search bar is active
|
||||||
|
final Color iconActiveColor;
|
||||||
|
|
||||||
|
/// Text style of the text in the search bar
|
||||||
|
final TextStyle textStyle;
|
||||||
|
|
||||||
|
/// Widget shown for cancellation
|
||||||
|
final Widget cancellationWidget;
|
||||||
|
|
||||||
|
/// Callback when cancel button is triggered
|
||||||
|
final VoidCallback? onCancelled;
|
||||||
|
|
||||||
|
/// Controller used to be able to sort, filter or replay the search
|
||||||
|
final SearchBarController? searchBarController;
|
||||||
|
|
||||||
|
/// Enable to edit the style of the search bar
|
||||||
|
final SearchBarStyle searchBarStyle;
|
||||||
|
|
||||||
|
/// Number of items displayed on cross axis
|
||||||
|
final int crossAxisCount;
|
||||||
|
|
||||||
|
/// Weather the list should take the minimum place or not
|
||||||
|
final bool shrinkWrap;
|
||||||
|
|
||||||
|
/// Called to get the tile at the specified index for the
|
||||||
|
/// [SliverGridStaggeredTileLayout].
|
||||||
|
final IndexedScaledTileBuilder? indexedScaledTileBuilder;
|
||||||
|
|
||||||
|
/// Set the scrollDirection
|
||||||
|
final Axis scrollDirection;
|
||||||
|
|
||||||
|
/// Spacing between tiles on main axis
|
||||||
|
final double mainAxisSpacing;
|
||||||
|
|
||||||
|
/// Spacing between tiles on cross axis
|
||||||
|
final double crossAxisSpacing;
|
||||||
|
|
||||||
|
/// Set a padding on the search bar
|
||||||
|
final EdgeInsetsGeometry searchBarPadding;
|
||||||
|
|
||||||
|
/// Set a padding on the header
|
||||||
|
final EdgeInsetsGeometry headerPadding;
|
||||||
|
|
||||||
|
/// Set a padding on the list
|
||||||
|
final EdgeInsetsGeometry listPadding;
|
||||||
|
|
||||||
|
SearchBar({
|
||||||
|
Key? key,
|
||||||
|
required this.onSearch,
|
||||||
|
required this.onItemFound,
|
||||||
|
this.searchBarController,
|
||||||
|
this.minimumChars = 3,
|
||||||
|
this.debounceDuration = const Duration(milliseconds: 500),
|
||||||
|
this.loader = const Center(child: CircularProgressIndicator()),
|
||||||
|
this.onError,
|
||||||
|
this.emptyWidget = const SizedBox.shrink(),
|
||||||
|
this.header,
|
||||||
|
this.placeHolder,
|
||||||
|
this.icon = const Icon(Icons.search),
|
||||||
|
this.hintText = "",
|
||||||
|
this.hintStyle = const TextStyle(color: Color.fromRGBO(142, 142, 147, 1)),
|
||||||
|
this.iconActiveColor = Colors.black,
|
||||||
|
this.textStyle = const TextStyle(color: Colors.black),
|
||||||
|
this.cancellationWidget = const Text("Cancel"),
|
||||||
|
this.onCancelled,
|
||||||
|
this.suggestions = const [],
|
||||||
|
this.buildSuggestion,
|
||||||
|
this.searchBarStyle = const SearchBarStyle(),
|
||||||
|
this.crossAxisCount = 1,
|
||||||
|
this.shrinkWrap = false,
|
||||||
|
this.indexedScaledTileBuilder,
|
||||||
|
this.scrollDirection = Axis.vertical,
|
||||||
|
this.mainAxisSpacing = 0.0,
|
||||||
|
this.crossAxisSpacing = 0.0,
|
||||||
|
this.listPadding = const EdgeInsets.all(0),
|
||||||
|
this.searchBarPadding = const EdgeInsets.all(0),
|
||||||
|
this.headerPadding = const EdgeInsets.all(0),
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SearchBarState createState() => _SearchBarState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchBarState<T> extends State<SearchBar<T>>
|
||||||
|
with TickerProviderStateMixin, _ControllerListener<T> {
|
||||||
|
bool _loading = false;
|
||||||
|
Widget? _error;
|
||||||
|
final _searchQueryController = TextEditingController();
|
||||||
|
Timer? _debounce;
|
||||||
|
bool _animate = false;
|
||||||
|
List<T> _list = [];
|
||||||
|
SearchBarController? searchBarController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
searchBarController =
|
||||||
|
widget.searchBarController ?? SearchBarController<T>();
|
||||||
|
searchBarController?.setListener(this);
|
||||||
|
searchBarController?.setTextController(_searchQueryController, widget.minimumChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onListChanged(List<T> items) {
|
||||||
|
setState(() {
|
||||||
|
_loading = false;
|
||||||
|
_list = items;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onLoading() {
|
||||||
|
setState(() {
|
||||||
|
_loading = true;
|
||||||
|
_error = null;
|
||||||
|
_animate = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClear() {
|
||||||
|
_cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(Error error) {
|
||||||
|
setState(() {
|
||||||
|
_loading = false;
|
||||||
|
_error = widget.onError != null ? widget.onError!(error) : Text("error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onTextChanged(String newText) async {
|
||||||
|
if (_debounce?.isActive ?? false) {
|
||||||
|
_debounce?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_debounce = Timer(widget.debounceDuration, () async {
|
||||||
|
if (newText.length >= widget.minimumChars && widget.onSearch != null) {
|
||||||
|
searchBarController?._search(newText, widget.onSearch);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_list.clear();
|
||||||
|
_error = null;
|
||||||
|
_loading = false;
|
||||||
|
_animate = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cancel() {
|
||||||
|
if (widget.onCancelled != null) {
|
||||||
|
widget.onCancelled!();
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_searchQueryController.clear();
|
||||||
|
_list.clear();
|
||||||
|
_error = null;
|
||||||
|
_loading = false;
|
||||||
|
_animate = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListView(
|
||||||
|
List<T> items, Widget Function(T item, int index) builder) {
|
||||||
|
return Padding(
|
||||||
|
padding: widget.listPadding,
|
||||||
|
child: StaggeredGridView.countBuilder(
|
||||||
|
crossAxisCount: widget.crossAxisCount,
|
||||||
|
itemCount: items.length,
|
||||||
|
shrinkWrap: widget.shrinkWrap,
|
||||||
|
staggeredTileBuilder:
|
||||||
|
widget.indexedScaledTileBuilder ?? (int index) => ScaledTile.fit(1),
|
||||||
|
scrollDirection: widget.scrollDirection,
|
||||||
|
mainAxisSpacing: widget.mainAxisSpacing,
|
||||||
|
crossAxisSpacing: widget.crossAxisSpacing,
|
||||||
|
addAutomaticKeepAlives: true,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return builder(items[index], index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget? _buildContent(BuildContext context) {
|
||||||
|
if (_error != null) {
|
||||||
|
return _error;
|
||||||
|
} else if (_loading) {
|
||||||
|
return widget.loader;
|
||||||
|
} else if (_searchQueryController.text.length < widget.minimumChars) {
|
||||||
|
if (widget.placeHolder != null) return widget.placeHolder;
|
||||||
|
return _buildListView(
|
||||||
|
widget.suggestions, widget.buildSuggestion ?? widget.onItemFound);
|
||||||
|
} else if (_list.isNotEmpty) {
|
||||||
|
return _buildListView(_list, widget.onItemFound);
|
||||||
|
} else {
|
||||||
|
return widget.emptyWidget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final widthMax = MediaQuery.of(context).size.width;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: widget.searchBarPadding,
|
||||||
|
child: Container(
|
||||||
|
height: 80,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Flexible(
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: Duration(milliseconds: 200),
|
||||||
|
width: _animate ? widthMax * .8 : widthMax,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: widget.searchBarStyle.borderRadius,
|
||||||
|
color: widget.searchBarStyle.backgroundColor,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: widget.searchBarStyle.padding,
|
||||||
|
child: Theme(
|
||||||
|
child: TextField(
|
||||||
|
controller: _searchQueryController,
|
||||||
|
onChanged: _onTextChanged,
|
||||||
|
style: widget.textStyle,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
icon: widget.icon,
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: widget.hintText,
|
||||||
|
hintStyle: widget.hintStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
primaryColor: widget.iconActiveColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _cancel,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: _animate ? 1.0 : 0,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
duration: Duration(milliseconds: _animate ? 1000 : 0),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: Duration(milliseconds: 200),
|
||||||
|
width:
|
||||||
|
_animate ? MediaQuery.of(context).size.width * .2 : 0,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Center(
|
||||||
|
child: widget.cancellationWidget,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: widget.headerPadding,
|
||||||
|
child: widget.header ?? Container(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildContent(context) ?? SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
|
|
||||||
|
/// Class only use in order to give access to StaggeredTile to users
|
||||||
|
class ScaledTile extends StaggeredTile {
|
||||||
|
/// Creates a [ScaledTile] with the given [crossAxisCellCount] that
|
||||||
|
/// fit its main axis extent to its content.
|
||||||
|
///
|
||||||
|
/// This tile will have a fixed main axis extent.
|
||||||
|
ScaledTile.fit(
|
||||||
|
int crossAxisCellCount,
|
||||||
|
) : super.fit(crossAxisCellCount);
|
||||||
|
|
||||||
|
/// Creates a [ScaledTile] with the given [crossAxisCellCount] and
|
||||||
|
/// [mainAxisExtent].
|
||||||
|
///
|
||||||
|
/// This tile will have a fixed main axis extent.
|
||||||
|
ScaledTile.extent(
|
||||||
|
int crossAxisCellCount,
|
||||||
|
double mainAxisExtent,
|
||||||
|
) : super.extent(crossAxisCellCount, mainAxisExtent);
|
||||||
|
|
||||||
|
/// Creates a [ScaledTile] with the given [crossAxisCellCount] and
|
||||||
|
/// [mainAxisCellCount].
|
||||||
|
///
|
||||||
|
/// The main axis extent of this tile will be the length of
|
||||||
|
/// [mainAxisCellCount] cells (inner spacings included).
|
||||||
|
ScaledTile.count(
|
||||||
|
int crossAxisCellCount,
|
||||||
|
num mainAxisCellCount,
|
||||||
|
) : super.count(crossAxisCellCount, mainAxisCellCount as double);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SearchBarStyle {
|
||||||
|
final Color backgroundColor;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
|
||||||
|
const SearchBarStyle(
|
||||||
|
{this.backgroundColor = const Color.fromRGBO(142, 142, 147, .15),
|
||||||
|
this.padding = const EdgeInsets.all(5.0),
|
||||||
|
this.borderRadius = const BorderRadius.all(Radius.circular(5.0))});
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- workflow
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Dart and Flutter Package Publisher
|
||||||
|
uses: k-paxian/dart-package-publisher@v1.2
|
||||||
|
with:
|
||||||
|
skiptests: false
|
||||||
|
dryRunOnly: false
|
||||||
|
credentialJson: ${{ secrets.CREDENTIAL_JSON }}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
|
### JetBrains ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
.gradle/
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
Vendored
+74
@@ -0,0 +1,74 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 8874f21e79d7ec66d0457c7ab338348e31b17f1d
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
||||||
Vendored
+290
@@ -0,0 +1,290 @@
|
|||||||
|
# Stripe SDK Changelog
|
||||||
|
|
||||||
|
## 5.0.0-nullsafety.1
|
||||||
|
|
||||||
|
## 4.0.2
|
||||||
|
|
||||||
|
* Fix a bug with date validation
|
||||||
|
|
||||||
|
## 4.0.1
|
||||||
|
|
||||||
|
* Fix assertion for webReturnPath
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
* Refactor, simplify and improve credit card validation in `StripeCard`
|
||||||
|
* Add support for postal code in `AddPaymentMethodScreen` and `StripeCard`
|
||||||
|
* Removed experimental status of `AddPaymentMethodScreen` and `PaymentMethodsScreen`
|
||||||
|
* List up to 100 payment methods in `PaymentMethodsScreen`, up from 10
|
||||||
|
* Add support for additional request parameters in `CustomerSession.listPaymentMethods`
|
||||||
|
* Misc UI improvements on `AddPaymentMethodScreen`, e.g proper autofill hints and more.
|
||||||
|
* Add new optional parameter `webReturnPath` to all SCA-capable methods. This allow specifying a custom return_url when compiled for web.
|
||||||
|
* Fix issue with iOS not closing webview after SCA.
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
|
||||||
|
This update removes many utility methods that were public, but not strictly related to Stripe or this library.
|
||||||
|
In most cases this will not affect you, but if you relied on any of the removed methods I recommend you copy the methods
|
||||||
|
into your project, or find other replacements.
|
||||||
|
|
||||||
|
* Removed many unused and unnecessary methods and constants on `StripeCard`
|
||||||
|
* Removed file `card_utils.dart` and all it's contents.
|
||||||
|
|
||||||
|
## 3.2.0
|
||||||
|
|
||||||
|
* Add new method: `StripeApi.createSource()`
|
||||||
|
|
||||||
|
## 3.1.0
|
||||||
|
|
||||||
|
* `CustomerSession` now implements `ChangeNotifier`, notifying listeners when the session is ended.
|
||||||
|
* `CustomerSession` will throw an error if attempted used after the session has ended.
|
||||||
|
* Added `CustomerSession.endSession()` instance method.
|
||||||
|
* `PaymentMethodStore` will now be cleared, listeners notified and finally disposed when the related `CustomerSession` is ended.
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* `CustomerSession.endCustomerSession()` has been deprecated and replaced by `CustomerSession.instance.endSession`.
|
||||||
|
|
||||||
|
## 3.0.1+2
|
||||||
|
|
||||||
|
* Update dependencies
|
||||||
|
* Fix misc warnings and lint issues
|
||||||
|
|
||||||
|
## 3.0.1+1
|
||||||
|
|
||||||
|
* Fix lint issues
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Fix unhandled exception due to unknown parameter: exp_mont
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Added Support for custom decorations in card form
|
||||||
|
* Updated default stripe api version to `2020-03-02`
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
* Removed `Card.toMap()`
|
||||||
|
* Removed support for non-card properties on Card (billing details)
|
||||||
|
* See https://github.com/ezet/stripe-sdk/issues/61#issuecomment-662657924
|
||||||
|
* Rename `confirmSetupIntentWithPaymentMethod` to `confirmSetupIntent` as this is the default, general case.
|
||||||
|
* Rename `confirmSetupIntent` to `authenticateSetupIntent` so it aligns more with `authenticatePaymentIntent`, as they share similar behavior.
|
||||||
|
* Make `returnUrlForSca` a required parameter for Stripe
|
||||||
|
|
||||||
|
#### Removed following deprecated functions and classes
|
||||||
|
* class: CardNumberFormatter
|
||||||
|
* constructor: CustomerSession
|
||||||
|
* Stripe.getReturnUrl
|
||||||
|
* Stripe.handlePaymentIntent
|
||||||
|
* Stripe.handleSetupIntent
|
||||||
|
|
||||||
|
## 2.8.2
|
||||||
|
|
||||||
|
* Updated readme
|
||||||
|
* Updated uni_links
|
||||||
|
* Added additional API documentation
|
||||||
|
* Fixed validation for expiration month
|
||||||
|
|
||||||
|
## 2.8.1
|
||||||
|
|
||||||
|
Please provide feedback on experimental features and examples.
|
||||||
|
|
||||||
|
* Fix misc minor bugs
|
||||||
|
* Fix misc lint issues
|
||||||
|
|
||||||
|
### Experimental: PaymentMethodsScreen
|
||||||
|
|
||||||
|
* Major improvements to UI and functionality
|
||||||
|
* Use a shared cache to avoid loading all payment methods repeatedly
|
||||||
|
* Create setup intent immediately upon entering add PM screen
|
||||||
|
|
||||||
|
### Experimental: CheckoutScreen
|
||||||
|
|
||||||
|
* Basic implementation of a checkout screen
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
* Misc changes related to PaymentMethodsScreen
|
||||||
|
* Started example implementation of CheckoutScreen
|
||||||
|
|
||||||
|
## 2.8.0
|
||||||
|
* Expose StripeApiException and StripeApiError
|
||||||
|
* Deprecated Stripe.handlePaymentIntent. Contact me if you used this.
|
||||||
|
* Deprecated Stripe.handleSetupIntent. Contact me if you used this.
|
||||||
|
* Deprecated `CustomerSession()` constructor. 3.0 will enforce the singleton pattern.
|
||||||
|
|
||||||
|
* Removed experimental parameter `nextAction` from `Stripe.authenticatePayment()`
|
||||||
|
* Added `Stripe.authenticatePaymentWithNextAction()`
|
||||||
|
|
||||||
|
### Experimental: PaymentMethodsScreen
|
||||||
|
A complete UI screen that lets a user view, delete and add stripe payment methods.
|
||||||
|
|
||||||
|
* Slide right to display delete option
|
||||||
|
* Press `+` to open `AddPaymentMethodScreen`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
* Re-organize example code
|
||||||
|
|
||||||
|
## 2.7.0
|
||||||
|
|
||||||
|
* Use explicit type for CreateSetupIntent return.
|
||||||
|
* Added optional [nextAction] parameter to Stripe.authenticatePayment.
|
||||||
|
* Make [paymentMethodId] of `Stripe.confirmPayment` optional
|
||||||
|
|
||||||
|
* Fix bug which prevented ephemeral keys from refreshing correctly.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
* Add [Customer Details]
|
||||||
|
* Add [Payments] with automatic and manual confirmation
|
||||||
|
|
||||||
|
## 2.6.0
|
||||||
|
|
||||||
|
* Add visual card widget in CardForm
|
||||||
|
|
||||||
|
* Fix misc bugs in CardForm
|
||||||
|
|
||||||
|
## 2.5.3
|
||||||
|
|
||||||
|
* Add focus handling for CardForm.
|
||||||
|
* Give card number focus by default.
|
||||||
|
* Enable tapping "next/arrow" on keyboard to move to next field.
|
||||||
|
|
||||||
|
## 2.5.2
|
||||||
|
|
||||||
|
* Added a complete demo application, available in /examples/app.
|
||||||
|
* Display, add and remove payment methods.
|
||||||
|
* Add payment methods with and without Setup Intent.
|
||||||
|
* Quickly test pre-made Stripe test cards, with SCA and more.
|
||||||
|
|
||||||
|
* Made the `StripeApi` instance on `Stripe` objects public. This avoids having to create
|
||||||
|
a separate StripeApi instance.
|
||||||
|
|
||||||
|
* Fixed bug which prevented ephemeral keys from refreshing correctly.
|
||||||
|
|
||||||
|
### Experimental:
|
||||||
|
|
||||||
|
* Added "AddPaymentMethod" screen, which handles the complete flow of adding a payment card.
|
||||||
|
* This is still WIP and in beta stage, meaning the API might change.
|
||||||
|
|
||||||
|
## 2.5.1
|
||||||
|
|
||||||
|
Minor breaking change.
|
||||||
|
|
||||||
|
* Rename returnUrlForSCA to returnUrlForSca.
|
||||||
|
|
||||||
|
|
||||||
|
## 2.5.0
|
||||||
|
|
||||||
|
* Add support for custom return url scheme
|
||||||
|
* Update documentation
|
||||||
|
|
||||||
|
## 2.4.5
|
||||||
|
|
||||||
|
* Add links to complete examples of the stripe SDK and supporting backend.
|
||||||
|
|
||||||
|
## 2.4.4
|
||||||
|
|
||||||
|
* Fix bug which caused initial expiry date not to be set
|
||||||
|
* Add runnable example in example/app
|
||||||
|
|
||||||
|
## 2.4.3
|
||||||
|
|
||||||
|
* Add support for custom text style on card form and form fields
|
||||||
|
* Added examples for card form and form fields
|
||||||
|
|
||||||
|
## 2.4.2
|
||||||
|
|
||||||
|
* Allow custom form field error text
|
||||||
|
* Fix bug where card number input field would allow more than 16 digits
|
||||||
|
|
||||||
|
|
||||||
|
## 2.4.1
|
||||||
|
|
||||||
|
* Add support for custom input decorators on card form and form fields
|
||||||
|
|
||||||
|
## 2.4.0
|
||||||
|
|
||||||
|
* Minor breaking change: Split package into two separate sub-libraries:
|
||||||
|
* stripe_sdk: API related functionality
|
||||||
|
* stripe_sdk_ui: UI widgets and utilities
|
||||||
|
|
||||||
|
* Replaced ListView with Column inside the CardForm
|
||||||
|
* Updated dependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2.3.0
|
||||||
|
|
||||||
|
* Add CardForm widget, which can be used to add or edit credit cards.
|
||||||
|
* Complete validation for card number, expiration date and CVC
|
||||||
|
* Individual FormField widgets can be used to create a custom form
|
||||||
|
* Fix bug in Stripe.authenticatePayment
|
||||||
|
|
||||||
|
## 2.2.0
|
||||||
|
|
||||||
|
* Add support for connected accounts
|
||||||
|
* Add optional constructor parameter `stripeAccount` for all APIs
|
||||||
|
|
||||||
|
## 2.1.1
|
||||||
|
|
||||||
|
* Remove unused stripeAccount property on `StripeApi`
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
* Add Stripe.confirmSetupIntentWithPaymentMethod
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Rewrite of internal API
|
||||||
|
* Fixed several issues and bugs
|
||||||
|
* Restructured public API with breaking changes
|
||||||
|
* Split `Stripe` into `StripeApi` and `Stripe`
|
||||||
|
* SCA related features moved to `Stripe`
|
||||||
|
* Moved basic Stripe API requests to `StripeApi`
|
||||||
|
|
||||||
|
See README and examples for further details details.
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
* Misc minor updates and fixes
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
* Complete support for SetupIntent with SCA
|
||||||
|
* Fix bug with confirmPayment
|
||||||
|
* Internal refactoring
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* Support multiple simultaneous authentication flows
|
||||||
|
* Improve documentation and examples
|
||||||
|
* Allow specifying apiVersion for CustomerSession
|
||||||
|
* Allow multiple instances of Stripe and CustomerSession
|
||||||
|
|
||||||
|
## 1.0.0+1
|
||||||
|
|
||||||
|
* Improve examples
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Improve API for SCA-related features
|
||||||
|
* Improve examples
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
* Remove typed models
|
||||||
|
* Add some support for payment intents
|
||||||
|
* Complete support for payment methods
|
||||||
|
* Complete support for tokens
|
||||||
|
* Add examples
|
||||||
|
* Major cleanup
|
||||||
|
|
||||||
|
## 0.0.1+2
|
||||||
|
|
||||||
|
* Add analysis_options.yaml
|
||||||
|
* Fix dartanalyzer issues
|
||||||
|
* Fix other misc packaging issues
|
||||||
|
|
||||||
|
## 0.0.1+1
|
||||||
|
|
||||||
|
* Initial release
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
Copyright (c) 2020, Lars-Kristian Dahl
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The views and conclusions contained in the software and documentation are those
|
||||||
|
of the authors and should not be interpreted as representing official policies,
|
||||||
|
either expressed or implied, of the Stripe SDK project.
|
||||||
Vendored
+247
@@ -0,0 +1,247 @@
|
|||||||
|
[](https://pub.dev/packages/stripe_sdk)
|
||||||
|
|
||||||
|
# Flutter Stripe SDK
|
||||||
|
|
||||||
|
A native dart package for Stripe. There are various other flutter plugins that wrap existing Stripe libraries,
|
||||||
|
but this package uses a different approach.
|
||||||
|
It does not wrap existing Stripe libraries, but instead accesses the Stripe API directly.
|
||||||
|
|
||||||
|
Flutter support:
|
||||||
|
* [x] iOS
|
||||||
|
* [x] Android
|
||||||
|
* [x] Web
|
||||||
|
|
||||||
|
See *example/main.dart* for additional short examples.
|
||||||
|
|
||||||
|
See <https://github.com/ezet/stripe-sdk/tree/master/example> for a complete demo application,
|
||||||
|
with a working example backend.
|
||||||
|
|
||||||
|
Demo backend: <https://github.com/ezet/stripe-sdk-demo-api>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Supports all types of SCA, including 3DS, 3DS2, BankID and others.
|
||||||
|
* Handle payments with complete SCA support.
|
||||||
|
* Add, remove and update payment methods, sources and cards, optionally with SCA.
|
||||||
|
* Manage customer information.
|
||||||
|
* Create all types of Stripe tokens.
|
||||||
|
* Forms, widgets and utilities to use directly, or create your own UI!
|
||||||
|
|
||||||
|
### Experimental
|
||||||
|
|
||||||
|
* Managed UI flow for adding payment methods with SCA (using SetupIntent).
|
||||||
|
|
||||||
|
### Supported APIs
|
||||||
|
|
||||||
|
- PaymentIntent, with SCA
|
||||||
|
- SetupIntent, with SCA
|
||||||
|
- PaymentMethod
|
||||||
|
- Customer
|
||||||
|
- Cards
|
||||||
|
- Sources
|
||||||
|
- Tokens
|
||||||
|
|
||||||
|
### Planned features
|
||||||
|
|
||||||
|
- Offer managed UI flow for checkout
|
||||||
|
|
||||||
|
## Demo application
|
||||||
|
|
||||||
|
There is a complete demo application available at <https://github.com/ezet/stripe-sdk/tree/master/example/app>.
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/ezet/stripe-sdk/master/doc/demo.png" width="300">
|
||||||
|
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- The return type for each function is `Future<Map<String, dynamic>>`, where the value depends on the stripe API version.
|
||||||
|
|
||||||
|
The library has three classes to access the Stripe API:
|
||||||
|
|
||||||
|
- `Stripe` for generic, non-customer specific APIs, using publishable keys.
|
||||||
|
- `CustomerSession` for customer-specific APIs, using stripe ephemeral keys.
|
||||||
|
- `StripeApi` enables raw REST calls against the Stripe API.
|
||||||
|
|
||||||
|
|
||||||
|
### Stripe
|
||||||
|
|
||||||
|
- <https://stripe.dev/stripe-android/index.html?com/stripe/android/Stripe.html>
|
||||||
|
|
||||||
|
Aims to provide high-level functionality similar to the official mobile Stripe SDKs.
|
||||||
|
|
||||||
|
### CustomerSession
|
||||||
|
|
||||||
|
_Requires a Stripe ephemeral key._
|
||||||
|
|
||||||
|
- <https://stripe.com/docs/mobile/android/customer-information#customer-session-no-ui>
|
||||||
|
- <https://stripe.com/docs/mobile/android/standard#creating-ephemeral-keys>
|
||||||
|
|
||||||
|
Provides functionality similar to CustomerSession in the Stripe Android SDK.
|
||||||
|
|
||||||
|
### StripeApi
|
||||||
|
|
||||||
|
- <https://stripe.com/docs/api>
|
||||||
|
|
||||||
|
Provides basic low-level methods to access the Stripe REST API.
|
||||||
|
|
||||||
|
- Limited to the APIs that can be used with a public key or ephemeral key.
|
||||||
|
- Library methods map to a Stripe API call with the same name.
|
||||||
|
- Additional parameters can be provided as an optional argument.
|
||||||
|
|
||||||
|
|
||||||
|
_`Stripe` and `CustomerSession` use this internally._
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
|
||||||
|
All classes offer a singleton instance that can be initiated by calling the `init(...)` methods and then accessed through `.instance`.
|
||||||
|
Regular instances can also be created using the constructor, which allows them to be managed by e.g. dependency injection instead.
|
||||||
|
|
||||||
|
### Stripe
|
||||||
|
|
||||||
|
```dart
|
||||||
|
Stripe.init('pk_xxx');
|
||||||
|
// or, to manage your own instance, or multiple instances
|
||||||
|
final stripe = Stripe('pk_xxx');
|
||||||
|
```
|
||||||
|
|
||||||
|
### CustomerSession
|
||||||
|
|
||||||
|
The function that retrieves the ephemeral key must return the JSON response as a plain string.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
CustomerSession.init((apiVersion) => server.getEphemeralKeyFromServer(apiVersion));
|
||||||
|
// or, to manage your own instances
|
||||||
|
final session = CustomerSession((apiVersion) => server.getEphemeralKeyFromServer(apiVersion));
|
||||||
|
```
|
||||||
|
|
||||||
|
### StripeApi
|
||||||
|
|
||||||
|
```dart
|
||||||
|
StripeApi.init('pk_xxx');
|
||||||
|
// or, to manage your own instances
|
||||||
|
final stripeApi = StripeApi('pk_xxx');
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI
|
||||||
|
|
||||||
|
Use `CardForm` to add or edit credit card details, or build your own form using the pre-built FormFields.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final card = StripeCard();
|
||||||
|
|
||||||
|
final form = CardForm(card: card, formKey: formKey);
|
||||||
|
|
||||||
|
onPressed: () {
|
||||||
|
if (formKey.currentState.validate()) {
|
||||||
|
formKey.currentState.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/ezet/stripe-sdk/master/doc/cardform.png" width="300">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## SCA/PSD2
|
||||||
|
|
||||||
|
The library offers complete support for SCA on iOS and Android.
|
||||||
|
It handles all types of SCA, including 3DS, 3DS2, BankID and others.
|
||||||
|
It handles SCA by launching the authentication flow in a web browser, and returns the result to the app.
|
||||||
|
The `returnUrlForSca` parameter must match the configuration of your `AndroidManifest.xml` and `Info.plist` as shown in the next steps.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
Stripe.init('pk_xxx', returnUrlForSca: 'stripesdk://3ds.stripesdk.io');
|
||||||
|
final clientSecret = await server.createPaymentIntent(Stripe.instance.getReturnUrlForSca());
|
||||||
|
final paymentIntent = await Stripe.instance.confirmPayment(clientSecret, paymentMethodId: 'pm_card_visa');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
You need to declare the following intent filter in `android/app/src/main/AndroidManifest.xml`.
|
||||||
|
This example is for the url `stripesdk://3ds.stripesdk.io`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<manifest ...>
|
||||||
|
<!-- ... other tags -->
|
||||||
|
<application ...>
|
||||||
|
<activity ...>
|
||||||
|
|
||||||
|
<!-- The launchMode should be singleTop or singleTask,
|
||||||
|
to avoid launching a new instance of the app when SCA has been completed. -->
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ... other tags -->
|
||||||
|
|
||||||
|
<!-- Deep Links -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data
|
||||||
|
android:scheme="stripesdk"
|
||||||
|
android:host="3ds.stripesdk.io" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
```
|
||||||
|
|
||||||
|
### IOS
|
||||||
|
|
||||||
|
For iOS you need to declare the scheme in `ios/Runner/Info.plist` (or through Xcode's Target Info editor,
|
||||||
|
under URL Types). This example is for the url `stripesdk://3ds.stripesdk.io`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- ... other tags -->
|
||||||
|
<plist>
|
||||||
|
<dict>
|
||||||
|
<!-- ... other tags -->
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>3ds.stripesdk.io</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>stripesdk</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<!-- ... other tags -->
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Experimental
|
||||||
|
|
||||||
|
Experimental features are marked as `deprecated` and the API is subject to change until it is deemed stable.
|
||||||
|
Feel free to use these features but be aware that breaking changes might be introduced in minor updates.
|
||||||
|
|
||||||
|
### Add Payment Method
|
||||||
|
|
||||||
|
Use `AddPaymentMethodScreen.withSetupIntent(...)` to launch a managed UI flow for adding a payment method.
|
||||||
|
This will also handle SCA if required.
|
||||||
|
|
||||||
|
### Payment Methods
|
||||||
|
|
||||||
|
`PaymentMethodsScreen` offers a prebuilt UI that can:
|
||||||
|
|
||||||
|
* List all current payment methods
|
||||||
|
* Add new payment methods, using setup intents
|
||||||
|
* Delete existing payment methods
|
||||||
|
|
||||||
|
## Additional examples
|
||||||
|
|
||||||
|
### Glappen
|
||||||
|
This is a complete application, with a mobile client and a backend API.
|
||||||
|
Documentation is lacking, but it can serve as an example for more advanced use.
|
||||||
|
|
||||||
|
App: <https://github.com/ezet/glappen-client>
|
||||||
|
Backend: <https://github.com/ezet/glappen-firebase-api>
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
|
||||||
|
# Uncomment to specify additional rules.
|
||||||
|
#linter:
|
||||||
|
# rules:
|
||||||
|
# prefer_single_quotes: true
|
||||||
|
# deprecated_member_use_from_same_package: false
|
||||||
|
# missing_required_param: false
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
# errors:
|
||||||
|
# omit_local_variable_types: ignore
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: true
|
||||||
|
implicit-dynamic: true
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
import 'ephemeral_key_manager.dart';
|
||||||
|
import 'stripe_api_handler.dart';
|
||||||
|
|
||||||
|
class CustomerSession extends ChangeNotifier {
|
||||||
|
static const int keyRefreshBufferInSeconds = 30;
|
||||||
|
|
||||||
|
static CustomerSession? _instance;
|
||||||
|
|
||||||
|
final StripeApiHandler _apiHandler;
|
||||||
|
|
||||||
|
final EphemeralKeyManager _keyManager;
|
||||||
|
final String apiVersion;
|
||||||
|
|
||||||
|
bool isDisposed = false;
|
||||||
|
|
||||||
|
/// Create a new CustomerSession instance. Use this if you prefer to manage your own instances.
|
||||||
|
CustomerSession._(EphemeralKeyProvider provider, {this.apiVersion = defaultApiVersion, String? stripeAccount})
|
||||||
|
: _keyManager = EphemeralKeyManager(provider, keyRefreshBufferInSeconds),
|
||||||
|
_apiHandler = StripeApiHandler(stripeAccount: stripeAccount) {
|
||||||
|
_apiHandler.apiVersion = apiVersion;
|
||||||
|
_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initiate the customer session singleton instance.
|
||||||
|
/// If [prefetchKey] is true, fetch the ephemeral key immediately.
|
||||||
|
static void initCustomerSession(EphemeralKeyProvider provider,
|
||||||
|
{String apiVersion = defaultApiVersion, String? stripeAccount, bool prefetchKey = true}) {
|
||||||
|
CustomerSession._(provider, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||||
|
if (prefetchKey) {
|
||||||
|
_instance!._keyManager.retrieveEphemeralKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End the managed singleton customer session.
|
||||||
|
/// Call this when the current user logs out.
|
||||||
|
@Deprecated('Use CustomerSession.instance.endSession instead.')
|
||||||
|
static void endCustomerSession() {
|
||||||
|
_instance?.endSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End the managed singleton customer session.
|
||||||
|
/// Call this when the current user logs out.
|
||||||
|
void endSession() {
|
||||||
|
notifyListeners();
|
||||||
|
dispose();
|
||||||
|
isDisposed = true;
|
||||||
|
if (identical(_instance, this)) {
|
||||||
|
_instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current customer session
|
||||||
|
static CustomerSession get instance {
|
||||||
|
if (_instance == null) {
|
||||||
|
throw Exception(
|
||||||
|
'Attempted to get instance of CustomerSession before initialization. Please initialize a new session using [CustomerSession.initCustomerSession() first.]');
|
||||||
|
}
|
||||||
|
assert(_instance!._assertNotDisposed());
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the details for the current customer.
|
||||||
|
/// https://stripe.com/docs/api/customers/retrieve
|
||||||
|
Future<Map<String, dynamic>> retrieveCurrentCustomer() async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
final path = '/customers/${key.customerId}';
|
||||||
|
return _apiHandler.request(RequestMethod.get, path, key.secret, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List a Customer's PaymentMethods.
|
||||||
|
/// https://stripe.com/docs/api/payment_methods/list
|
||||||
|
Future<Map<String, dynamic>> listPaymentMethods(
|
||||||
|
{type = 'card', int? limit, String? endingBefore, String? startingAfter}) async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
const path = '/payment_methods';
|
||||||
|
final params = {'customer': key.customerId, 'type': type};
|
||||||
|
if (limit != null) params['limit'] = limit;
|
||||||
|
if (startingAfter != null) params['starting_after'] = startingAfter;
|
||||||
|
if (endingBefore != null) params['ending_before'] = endingBefore;
|
||||||
|
return _apiHandler.request(RequestMethod.get, path, key.secret, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach a PaymentMethod.
|
||||||
|
/// https://stripe.com/docs/api/payment_methods/attach
|
||||||
|
Future<Map<String, dynamic>> attachPaymentMethod(String? paymentMethodId) async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
final path = '/payment_methods/$paymentMethodId/attach';
|
||||||
|
final params = {'customer': key.customerId};
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, key.secret, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detach a PaymentMethod.
|
||||||
|
/// https://stripe.com/docs/api/payment_methods/detach
|
||||||
|
Future<Map<String, dynamic>> detachPaymentMethod(String? paymentMethodId) async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
final path = '/payment_methods/$paymentMethodId/detach';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, key.secret, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches a Source object to the Customer.
|
||||||
|
/// The source must be in a chargeable or pending state.
|
||||||
|
/// https://stripe.com/docs/api/sources/attach
|
||||||
|
Future<Map<String, dynamic>> attachSource(String sourceId) async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
final path = '/customers/${key.customerId}/sources';
|
||||||
|
final params = {'source': sourceId};
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, key.secret, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detaches a Source object from a Customer.
|
||||||
|
/// The status of a source is changed to consumed when it is detached and it can no longer be used to create a charge.
|
||||||
|
/// https://stripe.com/docs/api/sources/detach
|
||||||
|
Future<Map<String, dynamic>> detachSource(String sourceId) async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
final path = '/customers/${key.customerId}/sources/$sourceId';
|
||||||
|
return _apiHandler.request(RequestMethod.delete, path, key.secret, apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the specified customer by setting the values of the parameters passed.
|
||||||
|
/// https://stripe.com/docs/api/customers/update
|
||||||
|
Future<Map<String, dynamic>> updateCustomer(Map<String, dynamic> data) async {
|
||||||
|
assert(_assertNotDisposed());
|
||||||
|
final EphemeralKey key = await (_keyManager.retrieveEphemeralKey());
|
||||||
|
final path = '/customers/${key.customerId}';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, key.secret, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _assertNotDisposed() {
|
||||||
|
assert(() {
|
||||||
|
if (isDisposed) {
|
||||||
|
throw FlutterError('A $runtimeType was used after being disposed.\n'
|
||||||
|
'Once you have called dispose() on a $runtimeType, it can no longer be used.');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert' show json;
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'stripe_api_handler.dart';
|
||||||
|
import 'stripe_error.dart';
|
||||||
|
import 'util/stripe_json_utils.dart';
|
||||||
|
|
||||||
|
/// Function that takes a apiVersion and returns a Stripe ephemeral key response
|
||||||
|
typedef EphemeralKeyProvider = Future<String> Function(String apiVersion);
|
||||||
|
|
||||||
|
/// Represents a Stripe Ephemeral Key
|
||||||
|
class EphemeralKey {
|
||||||
|
static const String fieldCreated = 'created';
|
||||||
|
static const String fieldExpires = 'expires';
|
||||||
|
static const String fieldSecret = 'secret';
|
||||||
|
static const String fieldLiveMode = 'livemode';
|
||||||
|
static const String fieldObject = 'object';
|
||||||
|
static const String fieldId = 'id';
|
||||||
|
static const String fieldAssociatedObjects = 'associated_objects';
|
||||||
|
static const String fieldType = 'type';
|
||||||
|
|
||||||
|
static const String fieldNull = 'null';
|
||||||
|
|
||||||
|
late String _id;
|
||||||
|
late int _created;
|
||||||
|
late int _expires;
|
||||||
|
late bool _liveMode;
|
||||||
|
late String _customerId;
|
||||||
|
late String _object;
|
||||||
|
late String _secret;
|
||||||
|
late String _type;
|
||||||
|
late DateTime _createdAt;
|
||||||
|
late DateTime _expiresAt;
|
||||||
|
|
||||||
|
EphemeralKey.fromJson(Map<String, dynamic> json) {
|
||||||
|
// TODO might throw an error if ephemeralKey doesn't provide all fields.
|
||||||
|
_id = optString(json, fieldId)!;
|
||||||
|
_created = optInteger(json, fieldCreated)!;
|
||||||
|
_expires = optInteger(json, fieldExpires)!;
|
||||||
|
_liveMode = optBoolean(json, fieldLiveMode)!;
|
||||||
|
_customerId = json[fieldAssociatedObjects][0][fieldId];
|
||||||
|
_type = json[fieldAssociatedObjects][0][fieldType];
|
||||||
|
_object = optString(json, fieldObject)!;
|
||||||
|
_secret = optString(json, fieldSecret)!;
|
||||||
|
_createdAt = DateTime.fromMillisecondsSinceEpoch(_created * 1000);
|
||||||
|
_expiresAt = DateTime.fromMillisecondsSinceEpoch(_expires * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get id => _id;
|
||||||
|
|
||||||
|
int get created => _created;
|
||||||
|
|
||||||
|
int get expires => _expires;
|
||||||
|
|
||||||
|
String get customerId => _customerId;
|
||||||
|
|
||||||
|
bool get liveMode => _liveMode;
|
||||||
|
|
||||||
|
String get object => _object;
|
||||||
|
|
||||||
|
String get secret => _secret;
|
||||||
|
|
||||||
|
String get type => _type;
|
||||||
|
|
||||||
|
DateTime get createdAt => _createdAt;
|
||||||
|
|
||||||
|
DateTime get expiresAt => _expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EphemeralKeyManager {
|
||||||
|
EphemeralKey? _ephemeralKey;
|
||||||
|
final EphemeralKeyProvider ephemeralKeyProvider;
|
||||||
|
final int timeBufferInSeconds;
|
||||||
|
|
||||||
|
EphemeralKeyManager(this.ephemeralKeyProvider, this.timeBufferInSeconds);
|
||||||
|
|
||||||
|
/// Retrieve a ephemeral key.
|
||||||
|
/// Will fetch a new one using [EphemeralKeyProvider] if required.
|
||||||
|
Future<EphemeralKey> retrieveEphemeralKey() async {
|
||||||
|
if (_shouldRefreshKey()) {
|
||||||
|
String key;
|
||||||
|
try {
|
||||||
|
key = await ephemeralKeyProvider(defaultApiVersion);
|
||||||
|
} catch (error) {
|
||||||
|
log(error.toString());
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, dynamic> decodedKey = json.decode(key);
|
||||||
|
_ephemeralKey = EphemeralKey.fromJson(decodedKey);
|
||||||
|
} catch (error) {
|
||||||
|
log(error.toString());
|
||||||
|
final e = StripeApiError(null, {
|
||||||
|
StripeApiError.fieldMessage:
|
||||||
|
'Failed to parse Ephemeral Key, Please return the response as it is as you received from stripe server',
|
||||||
|
});
|
||||||
|
throw StripeApiException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ephemeralKey!;
|
||||||
|
} else {
|
||||||
|
return _ephemeralKey!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldRefreshKey() {
|
||||||
|
if (_ephemeralKey == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
final diff = _ephemeralKey!.expiresAt.difference(now);
|
||||||
|
return diff.inSeconds < timeBufferInSeconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
import 'package:credit_card_validator/credit_card_validator.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class StripeCard {
|
||||||
|
final _ccValidator = CreditCardValidator();
|
||||||
|
|
||||||
|
String? number;
|
||||||
|
String? cvc;
|
||||||
|
int? expMonth;
|
||||||
|
int? expYear;
|
||||||
|
String? last4;
|
||||||
|
String? postalCode;
|
||||||
|
|
||||||
|
StripeCard({
|
||||||
|
this.number,
|
||||||
|
this.cvc,
|
||||||
|
this.expMonth,
|
||||||
|
this.expYear,
|
||||||
|
this.last4,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Checks whether or not the {@link #number} field is valid.
|
||||||
|
///
|
||||||
|
/// @return {@code true} if valid, {@code false} otherwise.
|
||||||
|
bool isPostalCodeValid() {
|
||||||
|
return (postalCode != null && postalCode!.isNotEmpty)
|
||||||
|
? int.tryParse(postalCode!) != null
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether or not the {@link #number} field is valid.
|
||||||
|
///
|
||||||
|
/// @return {@code true} if valid, {@code false} otherwise.
|
||||||
|
bool validateNumber() {
|
||||||
|
return number != null ? _ccValidator.validateCCNum(number!).isValid : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether or not the {@link #expMonth} and {@link #expYear} fields represent a valid
|
||||||
|
/// expiry date.
|
||||||
|
///
|
||||||
|
/// @return {@code true} if valid, {@code false} otherwise
|
||||||
|
bool validateDate() {
|
||||||
|
return _ccValidator
|
||||||
|
.validateExpDate(
|
||||||
|
'${expMonth.toString().padLeft(2, '0')}/${expYear.toString().padLeft(2, '0')}')
|
||||||
|
.isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether or not the {@link #cvc} field is valid.
|
||||||
|
///
|
||||||
|
/// @return {@code true} if valid, {@code false} otherwise
|
||||||
|
bool validateCVC() {
|
||||||
|
if (cvc == null) return false;
|
||||||
|
return _ccValidator.validateCVV(cvc!, _ccValidator.validateCCNum(number!).ccType).isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a stripe hash that represents this card.
|
||||||
|
/// It only sets the type and card details. In order to add additional details such as name and address,
|
||||||
|
/// you need to insert these keys into the hash before submitting it.
|
||||||
|
Map<String, Object> toPaymentMethod() {
|
||||||
|
final Map<String, Object> map = {
|
||||||
|
'type': 'card',
|
||||||
|
'card': {
|
||||||
|
'number': number,
|
||||||
|
'cvc': cvc,
|
||||||
|
'exp_month': expMonth,
|
||||||
|
'exp_year': expYear,
|
||||||
|
},
|
||||||
|
'billing_details': {
|
||||||
|
'address': {'postal_code': postalCode}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_removeNullAndEmptyParams(map);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _removeNullAndEmptyParams(Map<String, Object?> mapToEdit) {
|
||||||
|
// Remove all null values; they cause validation errors
|
||||||
|
final List<String> keys = mapToEdit.keys.toList(growable: false);
|
||||||
|
for (String key in keys) {
|
||||||
|
final Object? value = mapToEdit[key];
|
||||||
|
if (value == null) {
|
||||||
|
mapToEdit.remove(key);
|
||||||
|
} else if (value is String) {
|
||||||
|
if (value.isEmpty) {
|
||||||
|
mapToEdit.remove(key);
|
||||||
|
}
|
||||||
|
} else if (value is Map) {
|
||||||
|
_removeNullAndEmptyParams(value as Map<String, Object?>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+268
@@ -0,0 +1,268 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'ui/stripe_web_view.dart';
|
||||||
|
import "package:universal_html/html.dart" as html;
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import 'stripe_api.dart';
|
||||||
|
import 'ui/stripe_ui.dart';
|
||||||
|
|
||||||
|
class Stripe {
|
||||||
|
/// Creates a new [Stripe] object. Use this constructor if you wish to handle the instance of this class by yourself.
|
||||||
|
/// Alternatively, use [Stripe.init] to create a singleton and access it through [Stripe.instance].
|
||||||
|
///
|
||||||
|
/// [publishableKey] is your publishable key, beginning with "pk_".
|
||||||
|
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||||
|
///
|
||||||
|
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||||
|
/// This is a optional parameter.
|
||||||
|
///
|
||||||
|
/// [returnUrlForSca] should be used to specify a unique return url for
|
||||||
|
/// Strong Customer Authentication (SCA) such as 3DS, 3DS2, BankID and others.
|
||||||
|
/// It is required to use your own app specific url scheme and host. This
|
||||||
|
/// parameter must match your "android/app/src/main/AndroidManifest.xml"
|
||||||
|
/// and "ios/Runner/Info.plist" configuration.
|
||||||
|
Stripe(String publishableKey, {String? stripeAccount})
|
||||||
|
: api = StripeApi(publishableKey, stripeAccount: stripeAccount);
|
||||||
|
|
||||||
|
final StripeApi api;
|
||||||
|
static Stripe? _instance;
|
||||||
|
|
||||||
|
/// Access the instance of Stripe by calling [Stripe.instance].
|
||||||
|
/// Throws an [Exception] if [Stripe.init] hasn't been called previously.
|
||||||
|
static Stripe get instance {
|
||||||
|
if (_instance == null) {
|
||||||
|
throw Exception('Attempted to get singleton instance of Stripe without initialization');
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the singleton instance of [Stripe]. Afterwards you can
|
||||||
|
/// use [Stripe.instance] to access the created instance.
|
||||||
|
///
|
||||||
|
/// [publishableKey] is your publishable key, beginning with "pk_".
|
||||||
|
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||||
|
///
|
||||||
|
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||||
|
/// This is a optional parameter.
|
||||||
|
///
|
||||||
|
/// [returnUrlForSca] should be used to specify a unique return url for
|
||||||
|
/// Strong Customer Authentication (SCA) such as 3DS, 3DS2, BankID and others.
|
||||||
|
/// It is required to use your own app specific url scheme and host. This
|
||||||
|
/// parameter must match your "android/app/src/main/AndroidManifest.xml"
|
||||||
|
/// and "ios/Runner/Info.plist" configuration.
|
||||||
|
static void init(String publishableKey, {String? stripeAccount}) {
|
||||||
|
_instance = Stripe(publishableKey, stripeAccount: stripeAccount);
|
||||||
|
StripeApi.init(publishableKey, stripeAccount: stripeAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a return URL that can be used to authenticate a single PaymentIntent.
|
||||||
|
/// This should be set on the intent before attempting to authenticate it.
|
||||||
|
String getReturnUrlForSca({String? webReturnUrl}) {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return webReturnUrl ?? StripeUiOptions.defaultWebReturnUrl;
|
||||||
|
} else {
|
||||||
|
final requestId = Random.secure().nextInt(99999999);
|
||||||
|
return '${StripeUiOptions.defaultMobileReturnUrl}?requestId=$requestId';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authenticate a SetupIntent
|
||||||
|
/// https://stripe.com/docs/api/setup_intents/confirm
|
||||||
|
Future<Map<String, dynamic>> authenticateSetupIntent(String clientSecret,
|
||||||
|
{String? webReturnPath, required BuildContext context}) async {
|
||||||
|
final Map<String, dynamic> intent = await api.confirmSetupIntent(
|
||||||
|
clientSecret,
|
||||||
|
data: {'return_url': getReturnUrlForSca(webReturnUrl: webReturnPath)},
|
||||||
|
);
|
||||||
|
return _handleSetupIntent(intent, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm and authenticate a SetupIntent
|
||||||
|
/// https://stripe.com/docs/api/setup_intents/confirm
|
||||||
|
Future<Map<String, dynamic>> confirmSetupIntent(String clientSecret, String paymentMethod,
|
||||||
|
{String? webReturnPath, required BuildContext context}) async {
|
||||||
|
var returnUrlForSca = getReturnUrlForSca(webReturnUrl: webReturnPath);
|
||||||
|
final Map<String, dynamic> intent = await api.confirmSetupIntent(
|
||||||
|
clientSecret,
|
||||||
|
data: {
|
||||||
|
'return_url': returnUrlForSca,
|
||||||
|
'payment_method': paymentMethod,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return _handleSetupIntent(intent, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm and authenticate a payment.
|
||||||
|
/// Returns the PaymentIntent.
|
||||||
|
/// https://stripe.com/docs/payments/payment-intents/android
|
||||||
|
Future<Map<String, dynamic>> confirmPayment(String paymentIntentClientSecret, BuildContext context,
|
||||||
|
{String? paymentMethodId}) async {
|
||||||
|
final data = {'return_url': getReturnUrlForSca()};
|
||||||
|
if (paymentMethodId != null) data['payment_method'] = paymentMethodId;
|
||||||
|
final Map<String, dynamic> paymentIntent = await api.confirmPaymentIntent(
|
||||||
|
paymentIntentClientSecret,
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
return _handlePaymentIntent(paymentIntent, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm and authenticate a payment with Google Pay.
|
||||||
|
/// /// [paymentResult] must be the result of requesting a Google Pay payment with the `pay` library.
|
||||||
|
/// Returns the PaymentIntent.
|
||||||
|
Future<Map<String, dynamic>> confirmPaymentWithGooglePay(BuildContext context,
|
||||||
|
{required String paymentIntentClientSecret, required Map<String, dynamic> paymentResult}) async {
|
||||||
|
final data = <String, dynamic>{'return_url': getReturnUrlForSca()};
|
||||||
|
final String token = paymentResult['paymentMethodData']['tokenizationData']['token'] as String;
|
||||||
|
final tokenJson = jsonDecode(token) as Map<String, dynamic>;
|
||||||
|
final tokenId = tokenJson['id'] as String;
|
||||||
|
data['payment_method_data'] = {
|
||||||
|
'type': 'card',
|
||||||
|
"card": {"token": tokenId}
|
||||||
|
};
|
||||||
|
final Map<String, dynamic> paymentIntent = await api.confirmPaymentIntent(
|
||||||
|
paymentIntentClientSecret,
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
return _handlePaymentIntent(paymentIntent, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm and authenticate a payment with Apple Pay.
|
||||||
|
/// [paymentResult] must be the result of requesting a Apple Pay paymet with the `pay` library.
|
||||||
|
/// Returns the PaymentIntent.
|
||||||
|
Future<Map<String, dynamic>> confirmPaymentWithApplePay(BuildContext context,
|
||||||
|
{required String paymentIntentClientSecret, required Map<String, dynamic> paymentResult}) async {
|
||||||
|
final tokenPayload = <String, dynamic>{};
|
||||||
|
tokenPayload['pk_token'] = paymentResult['token'];
|
||||||
|
final paymentMethod = _getPaymentMethod(paymentResult['paymentMethod']);
|
||||||
|
tokenPayload['pk_token_instrument_name'] = paymentMethod['displayName'];
|
||||||
|
tokenPayload['pk_token_payment_network'] = paymentMethod['network'];
|
||||||
|
tokenPayload['card'] = <String, dynamic>{};
|
||||||
|
tokenPayload['pk_token_transaction_id'] = paymentResult['transactionIdentifier'];
|
||||||
|
|
||||||
|
if (tokenPayload['pk_token_transaction_id'] == "Simulated Identifier") {
|
||||||
|
tokenPayload['pk_token_transaction_id'] =
|
||||||
|
"ApplePayStubs~4242424242424242~200~USD~${DateTime.now().millisecondsSinceEpoch}";
|
||||||
|
}
|
||||||
|
|
||||||
|
final stripeToken = await api.createToken(tokenPayload);
|
||||||
|
final tokenId = stripeToken['id'] as String;
|
||||||
|
|
||||||
|
final data = <String, dynamic>{'return_url': getReturnUrlForSca()};
|
||||||
|
data['payment_method_data'] = {
|
||||||
|
'type': 'card',
|
||||||
|
"card": {"token": tokenId}
|
||||||
|
};
|
||||||
|
final Map<String, dynamic> paymentIntent = await api.confirmPaymentIntent(
|
||||||
|
paymentIntentClientSecret,
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
return _handlePaymentIntent(paymentIntent, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _getPaymentMethod(dynamic paymentMethod) {
|
||||||
|
if (paymentMethod is String) {
|
||||||
|
return jsonDecode(paymentMethod) as Map<String, dynamic>;
|
||||||
|
} else {
|
||||||
|
return paymentMethod as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authenticate a payment.
|
||||||
|
/// Returns the PaymentIntent.
|
||||||
|
/// https://stripe.com/docs/payments/payment-intents/android-manual
|
||||||
|
Future<Map<String, dynamic>> authenticatePayment(String paymentIntentClientSecret, BuildContext context) async {
|
||||||
|
final Map<String, dynamic> paymentIntent = await api.retrievePaymentIntent(paymentIntentClientSecret);
|
||||||
|
return _handlePaymentIntent(paymentIntent, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authenticate a payment with [paymentIntent].
|
||||||
|
/// This is similar to [authenticatePayment] but is slightly more efficient,
|
||||||
|
/// as it avoids the request to the Stripe API to retrieve the action.
|
||||||
|
/// To use this, return the complete [paymentIntent] from your server.
|
||||||
|
Future<Map<String, dynamic>> _handlePaymentIntent(Map<String, dynamic> paymentIntent, BuildContext context) async {
|
||||||
|
return _authenticateIntent(paymentIntent, context, api.retrievePaymentIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Launch 3DS in a new browser window.
|
||||||
|
/// Returns a [Future] with the Stripe SetupIntent when the user completes or cancels authentication.
|
||||||
|
Future<Map<String, dynamic>> _handleSetupIntent(Map<String, dynamic> setupIntent, BuildContext context) async {
|
||||||
|
return _authenticateIntent(setupIntent, context, api.retrieveSetupIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _authenticateIntent(Map<String, dynamic> intent, BuildContext context,
|
||||||
|
Future<Map<String, dynamic>> Function(String clientSecret) getIntentFunction) async {
|
||||||
|
if (intent['status'] != 'requires_action') return intent;
|
||||||
|
final clientSecret = intent['client_secret'];
|
||||||
|
final action = intent['next_action'];
|
||||||
|
final String url = action['redirect_to_url']['url'];
|
||||||
|
final returnUri = Uri.parse(action['redirect_to_url']['return_url']);
|
||||||
|
|
||||||
|
if (kIsWeb) {
|
||||||
|
return _authenticateWithBrowser(context, url, returnUri, getIntentFunction, clientSecret);
|
||||||
|
} else {
|
||||||
|
await _authenticateWithWebView(context, url, returnUri);
|
||||||
|
return getIntentFunction(clientSecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _authenticateWithBrowser(BuildContext context, String url, Uri returnUri,
|
||||||
|
Future<Map<String, dynamic>> Function(String clientSecret) getIntentFunction, String clientSecret) async {
|
||||||
|
final completer = Completer<Map<String, dynamic>>();
|
||||||
|
|
||||||
|
late StreamSubscription<html.Event> subscription;
|
||||||
|
subscription = html.window.onFocus.listen((event) async {
|
||||||
|
final intent = await getIntentFunction(clientSecret);
|
||||||
|
if (intent['status'] != 'requires_action') {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
subscription.cancel();
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
completer.complete(intent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => SimpleDialog(
|
||||||
|
title: const Text("Awaiting authentication, please complete authentication in the opened window."),
|
||||||
|
children: [
|
||||||
|
SimpleDialogOption(
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
subscription.cancel();
|
||||||
|
completer.complete(getIntentFunction(clientSecret));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SimpleDialogOption(
|
||||||
|
child: const Text("Open new window"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
launchUrl(Uri.parse(url));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await launchUrl(Uri.parse(url));
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> _authenticateWithWebView(BuildContext context, String url, Uri returnUri) async {
|
||||||
|
return Navigator.push<bool?>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StripeWebView(
|
||||||
|
uri: url,
|
||||||
|
returnUri: returnUri,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'models/card.dart';
|
||||||
|
import 'stripe_api_handler.dart';
|
||||||
|
|
||||||
|
typedef IntentProvider = Future<Map<String, dynamic>> Function(Uri uri);
|
||||||
|
|
||||||
|
class StripeApi {
|
||||||
|
static StripeApi? _instance;
|
||||||
|
|
||||||
|
final StripeApiHandler _apiHandler;
|
||||||
|
|
||||||
|
final String publishableKey;
|
||||||
|
|
||||||
|
final String apiVersion;
|
||||||
|
|
||||||
|
/// Create a new instance, which can be used with e.g. dependency injection.
|
||||||
|
/// Throws a [Exception] if an invalid [publishableKey] has been submitted.
|
||||||
|
///
|
||||||
|
/// [publishableKey] is your publishable key, beginning with "pk_".
|
||||||
|
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||||
|
///
|
||||||
|
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||||
|
/// This is a optional parameter.
|
||||||
|
StripeApi(this.publishableKey, {this.apiVersion = defaultApiVersion, String? stripeAccount})
|
||||||
|
: _apiHandler = StripeApiHandler(stripeAccount: stripeAccount) {
|
||||||
|
_validateKey(publishableKey);
|
||||||
|
_apiHandler.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the managed singleton instance of [StripeApi].
|
||||||
|
/// Afterwards you can use [StripeApi.instance] to access the created instance.
|
||||||
|
///
|
||||||
|
/// [publishableKey] is your publishable key, beginning with "pk_".
|
||||||
|
/// Your can copy your key from https://dashboard.stripe.com/account/apikeys
|
||||||
|
///
|
||||||
|
/// [stripeAccount] is the id of a stripe customer and stats with "cus_".
|
||||||
|
/// This is a optional parameter.
|
||||||
|
static void init(String publishableKey, {String apiVersion = defaultApiVersion, String? stripeAccount}) {
|
||||||
|
_instance ??= StripeApi(publishableKey, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the singleton instance of [StripeApi].
|
||||||
|
/// Throws an [Exception] if [StripeApi.init] hasn't been called previously.
|
||||||
|
static StripeApi get instance {
|
||||||
|
if (_instance == null) {
|
||||||
|
throw Exception('Attempted to get singleton instance of StripeApi without initialization');
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a stripe Token
|
||||||
|
/// https://stripe.com/docs/api/tokens
|
||||||
|
Future<Map<String, dynamic>> createToken(Map<String, dynamic> data) async {
|
||||||
|
const path = '/tokens';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a PaymentMethod.
|
||||||
|
/// https://stripe.com/docs/api/payment_methods/create
|
||||||
|
Future<Map<String, dynamic>> createPaymentMethod(Map<String, dynamic> data) async {
|
||||||
|
const path = '/payment_methods';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a PaymentMethod from a card.
|
||||||
|
/// This will only create a PaymentMethod with the minimum required properties.
|
||||||
|
/// To include additional properties such as billing details, use [StripeCard.toPaymentMethod], add additional details
|
||||||
|
/// and then use [createPaymentMethod].
|
||||||
|
Future<Map<String, dynamic>> createPaymentMethodFromCard(StripeCard card) async {
|
||||||
|
return createPaymentMethod(card.toPaymentMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Source object.
|
||||||
|
/// https://stripe.com/docs/api/sources/create
|
||||||
|
Future<Map<String, dynamic>> createSource(Map<String, dynamic> data) async {
|
||||||
|
const path = '/sources';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a PaymentIntent.
|
||||||
|
/// https://stripe.com/docs/api/payment_intents/retrieve
|
||||||
|
Future<Map<String, dynamic>> retrievePaymentIntent(String clientSecret, {String? apiVersion}) async {
|
||||||
|
final intentId = _parseIdFromClientSecret(clientSecret);
|
||||||
|
final path = '/payment_intents/$intentId';
|
||||||
|
final params = {'client_secret': clientSecret};
|
||||||
|
return _apiHandler.request(RequestMethod.get, path, publishableKey, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm a PaymentIntent
|
||||||
|
/// https://stripe.com/docs/api/payment_intents/confirm
|
||||||
|
Future<Map<String, dynamic>> confirmPaymentIntent(String clientSecret, {Map<String, dynamic>? data}) async {
|
||||||
|
data ??= {};
|
||||||
|
final intent = _parseIdFromClientSecret(clientSecret);
|
||||||
|
data['client_secret'] = clientSecret;
|
||||||
|
final path = '/payment_intents/$intent/confirm';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a SetupIntent.
|
||||||
|
/// https://stripe.com/docs/api/setup_intents/retrieve
|
||||||
|
Future<Map<String, dynamic>> retrieveSetupIntent(String clientSecret, {String? apiVersion}) async {
|
||||||
|
final intentId = _parseIdFromClientSecret(clientSecret);
|
||||||
|
final path = '/setup_intents/$intentId';
|
||||||
|
final params = {'client_secret': clientSecret};
|
||||||
|
return _apiHandler.request(RequestMethod.get, path, publishableKey, apiVersion, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm a SetupIntent
|
||||||
|
/// https://stripe.com/docs/api/setup_intents/confirm
|
||||||
|
Future<Map<String, dynamic>> confirmSetupIntent(String clientSecret, {Map<String, dynamic>? data}) async {
|
||||||
|
data ??= {};
|
||||||
|
final intent = _parseIdFromClientSecret(clientSecret);
|
||||||
|
data['client_secret'] = clientSecret;
|
||||||
|
final path = '/setup_intents/$intent/confirm';
|
||||||
|
return _apiHandler.request(RequestMethod.post, path, publishableKey, apiVersion, params: data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the received [publishableKey] and throws a [Exception] if an
|
||||||
|
/// invalid key has been submitted.
|
||||||
|
static void _validateKey(String publishableKey) {
|
||||||
|
if (publishableKey.isEmpty) {
|
||||||
|
throw Exception('Invalid Publishable Key: '
|
||||||
|
'You must use a valid publishable key to create a token. '
|
||||||
|
'For more info, see https://stripe.com/docs/stripe.js.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publishableKey.startsWith('sk_')) {
|
||||||
|
throw Exception('Invalid Publishable Key: '
|
||||||
|
'You are using a secret key to create a token, '
|
||||||
|
'instead of the publishable one. For more info, '
|
||||||
|
'see https://stripe.com/docs/stripe.js');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _parseIdFromClientSecret(String clientSecret) {
|
||||||
|
return clientSecret.split('_secret')[0];
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert' show json;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import 'stripe_error.dart';
|
||||||
|
|
||||||
|
const String defaultApiVersion = '2020-03-02';
|
||||||
|
|
||||||
|
enum RequestMethod { get, post, put, delete, option }
|
||||||
|
|
||||||
|
class StripeApiHandler {
|
||||||
|
static const String liveApiBase = 'https://api.stripe.com';
|
||||||
|
static const String liveLogginBase = 'https://q.stripe.com';
|
||||||
|
static const String liveLoggingEndpoint = 'https://m.stripe.com/4';
|
||||||
|
static const String liveApiPath = liveApiBase + '/v1';
|
||||||
|
|
||||||
|
static const String charset = 'UTF-8';
|
||||||
|
static const String customers = 'customers';
|
||||||
|
static const String tokens = 'tokens';
|
||||||
|
static const String sources = 'sources';
|
||||||
|
|
||||||
|
static const String headerKeyRequestId = 'Request-Id';
|
||||||
|
static const String fieldError = 'error';
|
||||||
|
static const String fieldSource = 'source';
|
||||||
|
|
||||||
|
String apiVersion = defaultApiVersion;
|
||||||
|
|
||||||
|
static const String malformedResponseMessage = 'An improperly formatted error response was found.';
|
||||||
|
|
||||||
|
final http.Client _client = http.Client();
|
||||||
|
|
||||||
|
final String? stripeAccount;
|
||||||
|
|
||||||
|
StripeApiHandler({this.stripeAccount});
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> request(RequestMethod method, String path, String key, String? apiVersion,
|
||||||
|
{final Map<String, dynamic>? params}) {
|
||||||
|
final options = RequestOptions(key: key, apiVersion: apiVersion, stripeAccount: stripeAccount);
|
||||||
|
return _getStripeResponse(method, liveApiPath + path, options, params: params);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _getStripeResponse(RequestMethod method, final String url, final RequestOptions options,
|
||||||
|
{final Map<String, dynamic>? params}) async {
|
||||||
|
final Map<String, String> headers = _headers(options: options);
|
||||||
|
|
||||||
|
http.Response response;
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case RequestMethod.get:
|
||||||
|
var fUrl = url;
|
||||||
|
if (params != null && params.isNotEmpty) {
|
||||||
|
fUrl = '$url?${_encodeMap(params)}';
|
||||||
|
}
|
||||||
|
response = await _client.get(Uri.parse(fUrl), headers: headers);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestMethod.post:
|
||||||
|
response = await _client.post(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: headers,
|
||||||
|
body: params != null ? _urlEncodeMap(params) : null,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RequestMethod.delete:
|
||||||
|
response = await _client.delete(Uri.parse(url), headers: headers);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception('Request Method: $method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
final requestId = response.headers[headerKeyRequestId];
|
||||||
|
|
||||||
|
final statusCode = response.statusCode;
|
||||||
|
late Map<String, dynamic> resp;
|
||||||
|
try {
|
||||||
|
resp = json.decode(response.body);
|
||||||
|
} catch (error) {
|
||||||
|
final stripeError = StripeApiError(requestId, {StripeApiError.fieldMessage: malformedResponseMessage});
|
||||||
|
throw StripeApiException(stripeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode < 200 || statusCode >= 300) {
|
||||||
|
final Map<String, dynamic> errBody = resp[fieldError];
|
||||||
|
final stripeError = StripeApiError(requestId, errBody);
|
||||||
|
throw StripeApiException(stripeError);
|
||||||
|
} else {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
static Map<String, String> _headers({RequestOptions? options}) {
|
||||||
|
final Map<String, String> headers = {};
|
||||||
|
headers['Accept-Charset'] = charset;
|
||||||
|
headers['Accept'] = 'application/json';
|
||||||
|
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||||
|
headers['User-Agent'] = 'StripeSDK/v2';
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
headers['Authorization'] = 'Bearer ${options.key}';
|
||||||
|
|
||||||
|
if (options.apiVersion != null) {
|
||||||
|
headers['Stripe-Version'] = options.apiVersion!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.stripeAccount != null) {
|
||||||
|
headers['Stripe-Account'] = options.stripeAccount!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.idempotencyKey != null) {
|
||||||
|
headers['Idempotency-Key'] = options.idempotencyKey!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug headers
|
||||||
|
final propertyMap = <String, String>{};
|
||||||
|
propertyMap['os.name'] = defaultTargetPlatform.toString();
|
||||||
|
propertyMap['lang'] = 'Dart';
|
||||||
|
propertyMap['publisher'] = 'lars.dahl@gmail.com';
|
||||||
|
headers['X-Stripe-Client-User-Agent'] = json.encode(propertyMap);
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _encodeMap(Map<String, dynamic> params) {
|
||||||
|
return params.keys
|
||||||
|
.map((key) => '${Uri.encodeComponent(key)}=${Uri.encodeComponent(params[key].toString())}')
|
||||||
|
.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _urlEncodeMap(dynamic data) {
|
||||||
|
final urlData = StringBuffer();
|
||||||
|
var first = true;
|
||||||
|
void urlEncode(dynamic sub, String path) {
|
||||||
|
if (sub is List) {
|
||||||
|
for (var i = 0; i < sub.length; i++) {
|
||||||
|
urlEncode(sub[i], '$path%5B%5D');
|
||||||
|
}
|
||||||
|
} else if (sub is Map) {
|
||||||
|
sub.forEach((k, v) {
|
||||||
|
if (path == '') {
|
||||||
|
urlEncode(v, Uri.encodeQueryComponent(k));
|
||||||
|
} else {
|
||||||
|
urlEncode(v, '$path%5B${Uri.encodeQueryComponent(k)}%5D');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!first) {
|
||||||
|
urlData.write('&');
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
urlData.write('$path=${Uri.encodeQueryComponent(sub.toString())}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urlEncode(data, '');
|
||||||
|
return urlData.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RequestOptions {
|
||||||
|
static const String typeQuery = 'source';
|
||||||
|
static const String typeJson = 'json_data';
|
||||||
|
|
||||||
|
final String? apiVersion; // TODO might have no usage
|
||||||
|
final String? guid;
|
||||||
|
final String? idempotencyKey;
|
||||||
|
final String key;
|
||||||
|
final String? requestType; // TODO might have no usage
|
||||||
|
final String? stripeAccount;
|
||||||
|
|
||||||
|
RequestOptions({
|
||||||
|
required this.apiVersion,
|
||||||
|
this.guid,
|
||||||
|
this.idempotencyKey,
|
||||||
|
required this.key,
|
||||||
|
this.requestType,
|
||||||
|
this.stripeAccount,
|
||||||
|
});
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user