Compare commits

2 Commits
Author SHA1 Message Date
pei 909fdf46d6 final2 2026-07-12 04:34:44 +08:00
pei e7ce0f7bae final 2026-07-12 04:33:48 +08:00
194 changed files with 7492 additions and 2947 deletions
+1 -2
View File
@@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class ImageViewer extends StatefulWidget {
final Key key;
final ImageProvider imageProvider;
ImageViewer(this.imageProvider, {this.key}) :
ImageViewer(this.imageProvider, {Key? key}) :
super(key: key);
@override
+9 -12
View File
@@ -33,18 +33,15 @@ AlertDialog logoutDialog(BuildContext context) {
.yes_i_am_sure),
onPressed: () {
Navigator.of(context).pop();
Utils.getBox().then((box) {
box.delete(Constants.KEY_USER_ID);
box.delete(
Constants.KEY_ACCESS_TOKEN);
store.dispatch(
new UpdateCurrentUser(null));
eventBus.fire(
new OnCurrentUserUpdated());
Routes.router.navigateTo(
context, '/', replace: true,
clearStack: true);
});
Utils.prefs?.remove(Constants.KEY_USER_ID);
Utils.prefs?.remove(Constants.KEY_ACCESS_TOKEN);
store.dispatch(
new UpdateCurrentUser(null));
eventBus.fire(
new OnCurrentUserUpdated());
Routes.router.navigateTo(
context, '/', replace: true,
clearStack: true);
},
)
],
+1 -1
View File
@@ -87,7 +87,7 @@ class OnProductWillAddToCart {
double price;
String description;
Business business;
GlobalKey buttonKey;
GlobalKey? buttonKey;
OnProductWillAddToCart(this.product, this.selections, this.price, this.description, this.business, {this.buttonKey});
}
+16 -44
View File
@@ -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/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_redux/flutter_redux.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/me.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 'events/eventbus.dart';
import 'events/events.dart';
import 'generated/l10n.dart';
import 'pages/home.dart';
import 'pages/plain_page.dart';
@@ -34,20 +25,14 @@ import 'utils/utils.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Enable configureApp will cause route problem.
// configureApp();
setPathUrlStrategy();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
String _to = '/';
MyApp() {
Routes.configure();
Utils().init();
Util().init();
// getCurrentPosition();
}
static getCurrentPosition() async {
@@ -95,27 +80,8 @@ class MyApp extends StatelessWidget {
@override
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(
debugShowCheckedModeBanner: Constants.DEBUG,
navigatorKey: kIsWeb ? null : Catcher.navigatorKey,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
@@ -124,20 +90,26 @@ class MyApp extends StatelessWidget {
S.delegate,
],
supportedLocales: S.delegate.supportedLocales,
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) {
print('Language code: ${locale.languageCode}, Country code: ${locale.countryCode}');
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
store.dispatch(UpdateLocale(locale));
return supportedLocale;
localeResolutionCallback: (Locale? locale, Iterable<Locale>? supportedLocales) {
print('Language code: ${locale?.languageCode}, Country code: ${locale?.countryCode}');
if (locale != null && supportedLocales != null) {
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
store.dispatch(UpdateLocale(locale));
return supportedLocale;
}
}
}
store.dispatch(UpdateLocale(supportedLocales.first));
store.dispatch(UpdateLocale(supportedLocales.first));
return supportedLocales.first;
}
return null;
},
onGenerateRoute: (RouteSettings settings) {
final List<String> pathElements = settings.name.split('/');
print('path elements: $pathElements');
final uri = Uri.parse(settings.name!);
final path = uri.path;
print('path: $path');
final List<String> pathElements = path.split('/');
print('path elements --> $pathElements');
if (pathElements[0] != '') {
return null;
}
+15 -15
View File
@@ -1,21 +1,21 @@
import 'dart:convert';
class Address {
int id;
String addressLine1;
String addressLine2;
String city;
String state;
String country;
String zip;
String phone;
String fax;
String email;
String contactName;
int gender;
String lat;
String lng;
String fullAddress;
int? id;
String? addressLine1;
String? addressLine2;
String? city;
String? state;
String? country;
String? zip;
String? phone;
String? fax;
String? email;
String? contactName;
int? gender;
String? lat;
String? lng;
String? fullAddress;
Address.fromJson(Map<String, dynamic> json)
: id = json['id'],
+1 -1
View File
@@ -58,7 +58,7 @@ class Business {
String chaseXLogin;
String chaseTransactionKey;
String chaseResponseKey;
DistanceInfo distanceInfo;
DistanceInfo? distanceInfo;
int commentsCount;
bool forceClose;
bool isPublic;
+19 -19
View File
@@ -7,18 +7,18 @@ import 'cart_line_item.dart';
import 'extra_fee.dart';
class CartInfo {
int id;
double originPrice;
double discountPrice;
double totalPrice;
Business businessInfo;
List<CartLineItem> productList;
List<ExtraFee> extraFeeList;
List<ExtraFee> discountList;
int deliveryMethod;
String shippingMethod;
double amountPaid;
String extraData;
int? id;
double? originPrice;
double? discountPrice;
double? totalPrice;
Business? businessInfo;
List<CartLineItem>? productList;
List<ExtraFee>? extraFeeList;
List<ExtraFee>? discountList;
int? deliveryMethod;
String? shippingMethod;
double? amountPaid;
String? extraData;
CartInfo.fromJson(Map<String, dynamic> json)
: id = json['id'],
@@ -53,7 +53,7 @@ class CartInfo {
if (productList == null) {
productList = [item];
} else {
productList.add(item);
productList!.add(item);
}
}
@@ -74,19 +74,19 @@ class CartInfo {
originPrice = 0.0;
discountPrice = 0.0;
totalPrice = 0.0;
for (var i = 0; i < productList.length; i++) {
newTotal += productList[i].getTotalPrice();
for (var i = 0; i < productList!.length; i++) {
newTotal += productList![i].getTotalPrice();
}
originPrice = newTotal;
totalPrice = newTotal;
return totalPrice;
return totalPrice!;
}
int getProductListTotalQuantity() {
int qty = 0;
if (productList != null && productList.length > 0) {
for (var i = 0; i < productList.length; i++) {
qty += productList[i].quantity.round();
if (productList != null && productList!.length > 0) {
for (var i = 0; i < productList!.length; i++) {
qty += productList![i].quantity!.round();
}
}
return qty;
+11 -11
View File
@@ -6,15 +6,15 @@ import 'package:uuid/uuid.dart';
import 'product.dart';
class CartLineItem {
int id;
double unitPrice;
double totalPrice;
Product product;
String name;
String description;
double quantity;
String uuid;
String parentUuid;
int? id;
double? unitPrice;
double? totalPrice;
Product? product;
String? name;
String? description;
double? quantity;
String? uuid;
String? parentUuid;
CartLineItem() {
uuid = Uuid().v4();
@@ -48,7 +48,7 @@ class CartLineItem {
}
double getTotalPrice() {
totalPrice = unitPrice * quantity;
return totalPrice;
totalPrice = unitPrice! * quantity!;
return totalPrice!;
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ class Coupon {
String description;
String expirationDate;
double minAmount;
Business store;
Business? store;
double valueAmount;
bool isPercentage;
+3 -3
View File
@@ -4,9 +4,9 @@ import 'dart:convert';
import 'text_value.dart';
class DistanceInfo {
TextValue duration;
TextValue distance;
String status;
TextValue? duration;
TextValue? distance;
String? status;
DistanceInfo.fromJson(Map<String, dynamic> json)
: duration = json['duration'] != null ? TextValue.fromJson(json['duration']) : null,
+9 -7
View File
@@ -14,8 +14,8 @@ class Order {
int id;
int userId;
int businessId;
Business businessInfo;
CartInfo cartInfo;
Business? businessInfo;
CartInfo? cartInfo;
int status;
double originPrice;
double discountPrice;
@@ -23,7 +23,7 @@ class Order {
String consignee;
String phone;
String address;
Address shippingAddress;
Address? shippingAddress;
int payMethod;
String remark;
String orderNum;
@@ -33,11 +33,11 @@ class Order {
String paymentStatus; // UNPAID, PAID, PARTIALPAID
double amountPaid;
List<Fulfillment> fulfillments;
String extraData;
String? extraData;
bool hasComment;
double fee;
UserPosition shipperPosition;
DistanceInfo deliveryDistance;
DistanceInfo? deliveryDistance;
Order.fromJson(Map<String, dynamic> json)
: id = json['id'],
@@ -100,8 +100,10 @@ class Order {
double getSubtotal() {
double subtotal = 0.0;
for (CartLineItem lineItem in cartInfo.productList) {
subtotal += lineItem.getTotalPrice();
if (cartInfo != null) {
for (CartLineItem lineItem in cartInfo!.productList!) {
subtotal += lineItem.getTotalPrice();
}
}
return subtotal;
}
+13 -17
View File
@@ -6,19 +6,19 @@ import 'package:flutter/cupertino.dart';
import '../generated/l10n.dart';
class PaymentPlatform {
int id;
String name;
String code;
String method;
String icon;
String xLogin;
String transactionKey;
String responseKey;
String squareAppId;
String squareAccessToken;
String squareLocationId;
String publishableKey;
String merchantId;
int? id;
String? name;
String? code;
String? method;
String? icon;
String? xLogin;
String? transactionKey;
String? responseKey;
String? squareAppId;
String? squareAccessToken;
String? squareLocationId;
String? publishableKey;
String? merchantId;
PaymentPlatform(this.code);
@@ -57,16 +57,12 @@ class PaymentPlatform {
switch(code) {
case 'chase':
return S.of(context).credit_debit_card;
break;
case 'web-credit-card':
return S.of(context).credit_card;
break;
case 'ALIPAY':
return S.of(context).alipay;
break;
case 'WECHATPAY':
return S.of(context).wechatpay;
break;
case 'paypal':
return S.of(context).paypal;
case 'payondeliverypickup':
+1 -1
View File
@@ -3,7 +3,7 @@ class Position {
final double latitude;
final double longitude;
Position({this.latitude, this.longitude});
Position({required this.latitude, required this.longitude});
@override
bool operator ==(other) {
+17 -17
View File
@@ -5,23 +5,23 @@ import 'Subproduct.dart';
import 'product_attribute.dart';
class Product {
int id;
int businessId;
int categoryId;
String name;
double price;
double regularPrice;
String description;
String detailDescription;
String imagePath;
String secondImagePath;
double monthSales;
int rate;
double leftNum;
List<ProductAttribute> productAttributes;
bool nonInventory;
String extraData;
List<Subproduct> subproducts;
int? id;
int? businessId;
int? categoryId;
String? name;
double? price;
double? regularPrice;
String? description;
String? detailDescription;
String? imagePath;
String? secondImagePath;
double? monthSales;
int? rate;
double? leftNum;
List<ProductAttribute>? productAttributes;
bool? nonInventory;
String? extraData;
List<Subproduct>? subproducts;
Product(int id, int businessId, String name, double price, String description,
String imagePath, List<ProductAttribute> productAttributes) {
+1 -1
View File
@@ -10,7 +10,7 @@ class ProductAttribute {
bool singleSelection;
bool byQuantity;
bool required;
String extra;
String? extra;
bool disabled;
ProductAttribute.fromJson(Map<String, dynamic> json)
+2 -2
View File
@@ -4,8 +4,8 @@ import 'dart:convert';
import 'waiting_line_item.dart';
class WaitingLine {
List<WaitingLineItem> waitingLines;
String storeSn;
List<WaitingLineItem>? waitingLines;
String? storeSn;
WaitingLine.fromJson(Map<String, dynamic> json)
: waitingLines = (json['waiting_lines'] as List).map((i) => WaitingLineItem.fromJson(i)).toList(),
+6 -9
View File
@@ -8,19 +8,16 @@ import '../models/product.dart';
class AttributeSelection extends StatelessWidget {
final Product product;
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
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileAttributeSelection(product: product, business: business, startKey: startKey,),
tablet: MobileAttributeSelection(product: product, business: business, startKey: startKey,),
desktop: MobileAttributeSelection(product: product, business: business, startKey: startKey,),
),
return ScreenTypeLayout.builder(
mobile: (context) => MobileAttributeSelection(product: product, business: business, startKey: startKey),
tablet: (context) => MobileAttributeSelection(product: product, business: business, startKey: startKey),
desktop: (context) => MobileAttributeSelection(product: product, business: business, startKey: startKey),
);
}
+8 -12
View File
@@ -8,24 +8,20 @@ import '../widgets/desktop/desktop_blog.dart';
import '../widgets/mobile/mobile_blog.dart';
class Blog extends StatelessWidget {
final Key key;
final int businessId;
const Blog({this.key, int businessId}) :
const Blog({Key? key, required int businessId}) :
businessId = businessId ?? Constants.BUSINESS_ID;
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileBlog(businessId: businessId,),
tablet: DesktopBlog(businessId: businessId,),
desktop: Scrollbar(
isAlwaysShown: true,
child: DesktopBlog(businessId: businessId,),
),
),
return ScreenTypeLayout.builder(
mobile: (context) => MobileBlog(businessId: businessId),
tablet: (context) => DesktopBlog(businessId: businessId),
desktop: (context) => Scrollbar(
thumbVisibility: true,
child: DesktopBlog(businessId: businessId),
),
);
}
+9 -12
View File
@@ -19,10 +19,10 @@ import '../widgets/mobile/mobile_renew_license.dart';
class BuyService extends StatefulWidget {
final int gid;
final String serviceName;
final String domain;
final int sid;
final String? domain;
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);
@override
@@ -30,7 +30,7 @@ class BuyService extends StatefulWidget {
}
class BuyServiceState extends State<BuyService> {
Map<String, dynamic> data;
Map<String, dynamic>? data;
@override
Widget build(BuildContext context) {
@@ -48,13 +48,10 @@ class BuyServiceState extends State<BuyService> {
);
}
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileBuyService(data),
tablet: DesktopBuyService(data),
desktop: DesktopBuyService(data),
),
return ScreenTypeLayout.builder(
mobile: (context) => MobileBuyService(data),
tablet: (context) => DesktopBuyService(data),
desktop: (context) => DesktopBuyService(data),
);
}
@@ -73,7 +70,7 @@ class BuyServiceState extends State<BuyService> {
}
).then((value) {
data = value;
data['domain'] = widget.domain;
data?['domain'] = widget.domain;
print('data: $data');
setState(() {});
}).onError((error, stackTrace) {
+9 -9
View File
@@ -15,7 +15,7 @@ import '../widgets/mobile/mobile_change_mobile_or_email.dart';
class ChangeMobileOrEmail extends StatelessWidget {
final bool isMobile;
const ChangeMobileOrEmail(this.isMobile, {Key key}) : super(key: key);
const ChangeMobileOrEmail(this.isMobile, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -31,15 +31,15 @@ class ChangeMobileOrEmail extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileChangeMobileOrEmail(isMobile),
tablet: DesktopChangeMobileOrEmail(isMobile),
desktop: DesktopChangeMobileOrEmail(isMobile),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileChangeMobileOrEmail(isMobile),
tablet: (context) => DesktopChangeMobileOrEmail(isMobile),
desktop: (context) => DesktopChangeMobileOrEmail(isMobile),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+8 -8
View File
@@ -29,15 +29,15 @@ class ChangePassword extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileChangePassword(),
tablet: DesktopChangePassword(),
desktop: DesktopChangePassword(),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileChangePassword(),
tablet: (context) => DesktopChangePassword(),
desktop: (context) => DesktopChangePassword(),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+5 -5
View File
@@ -11,7 +11,7 @@ import '../widgets/mobile/mobile_checkout.dart';
class Checkout extends StatelessWidget {
final int businessId;
const Checkout(this.businessId, {Key key}) :
const Checkout(this.businessId, {Key? key}) :
super(key: key);
@override
@@ -20,10 +20,10 @@ class Checkout extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileCheckout(businessId),
tablet: DesktopCheckout(businessId),
desktop: DesktopCheckout(businessId),
ScreenTypeLayout.builder(
mobile: (context) => MobileCheckout(businessId),
tablet: (context) => DesktopCheckout(businessId),
desktop: (context) => DesktopCheckout(businessId),
),
);
}
+6 -6
View File
@@ -14,7 +14,7 @@ import '../widgets/mobile/mobile_contact_us.dart';
class ContactUs extends StatefulWidget {
final int businessId;
const ContactUs({this.businessId, Key key}) :
const ContactUs({required this.businessId, Key? key}) :
super(key: key);
@override
@@ -22,7 +22,7 @@ class ContactUs extends StatefulWidget {
}
class ContactUsState extends State<ContactUs> {
Business business;
Business? business;
@override
Widget build(BuildContext context) {
@@ -42,10 +42,10 @@ class ContactUsState extends State<ContactUs> {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileContactUs(business),
tablet: DesktopContactUs(business),
desktop: DesktopContactUs(business),
ScreenTypeLayout.builder(
mobile: (context) => MobileContactUs(business!),
tablet: (context) => DesktopContactUs(business!),
desktop: (context) => DesktopContactUs(business!),
),
);
}
+5 -5
View File
@@ -10,7 +10,7 @@ import '../widgets/mobile/mobile_coupons.dart';
class Coupons extends StatelessWidget {
final int contactId;
const Coupons(this.contactId, {Key key}) :
const Coupons(this.contactId, {Key? key}) :
super(key: key);
@@ -20,10 +20,10 @@ class Coupons extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileCoupons(contactId,),
tablet: DesktopCoupons(contactId,),
desktop: DesktopCoupons(contactId,),
ScreenTypeLayout.builder(
mobile: (context) => MobileCoupons(contactId,),
tablet: (context) => DesktopCoupons(contactId,),
desktop: (context) => DesktopCoupons(contactId,),
),
);
}
+5 -5
View File
@@ -11,7 +11,7 @@ import '../widgets/mobile/create_online_store_1.dart' as mobile;
class CreateOnlineStore1 extends StatefulWidget {
const CreateOnlineStore1({Key key}) :
const CreateOnlineStore1({Key? key}) :
super(key: key);
@override
@@ -33,10 +33,10 @@ class CreateOnlineStore1State extends State<CreateOnlineStore1> {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: mobile.CreateOnlineStore1(Constants.BUSINESS_ID),
tablet: desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
desktop: desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
ScreenTypeLayout.builder(
mobile: (context) => mobile.CreateOnlineStore1(Constants.BUSINESS_ID),
tablet: (context) => desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
desktop: (context) => desktop.CreateOnlineStore1(Constants.BUSINESS_ID),
),
);
}
+12 -12
View File
@@ -30,7 +30,7 @@ class Download extends StatefulWidget {
class DownloadState extends State<Download> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
Map<String, dynamic> data;
Map<String, dynamic>? data;
@override
Widget build(BuildContext context) {
@@ -57,18 +57,18 @@ class DownloadState extends State<Download> {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null,
body: ScreenTypeLayout(
mobile: MobileDownloadApps(data),
tablet: DesktopDownloadApps(data),
desktop: Scrollbar(
isAlwaysShown: true,
child: DesktopDownloadApps(data),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileDownloadApps(data!),
tablet: (context) => DesktopDownloadApps(data!),
desktop: (context) => Scrollbar(
thumbVisibility: true,
child: DesktopDownloadApps(data!),
),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
@@ -79,7 +79,7 @@ class DownloadState extends State<Download> {
super.initState();
eventBus.on<OpenDrawer>().listen((event) {
if (mounted) {
_scaffoldKey.currentState.openDrawer();
_scaffoldKey.currentState?.openDrawer();
}
});
_loadData();
+7 -8
View File
@@ -13,20 +13,19 @@ import '../widgets/general/navigationbar.dart';
import '../widgets/mobile/MobileBottomNav.dart';
class EditAddress extends StatelessWidget {
final Key key;
final Address address;
final int businessId;
const EditAddress(this.address, {this.key, int businessId}) :
final Address? address;
final int? businessId;
const EditAddress(this.address, {Key? key, int? businessId}) :
businessId = businessId ?? Constants.BUSINESS_ID;
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileEditAddress(address, businessId: businessId,),
tablet: DesktopEditAddress(address, businessId: businessId,),
desktop: DesktopEditAddress(address, businessId: businessId,),
ScreenTypeLayout.builder(
mobile: (context) => MobileEditAddress(address, businessId: businessId,),
tablet: (context) => DesktopEditAddress(address, businessId: businessId,),
desktop: (context) => DesktopEditAddress(address, businessId: businessId,),
),
);
}
+9 -9
View File
@@ -12,7 +12,7 @@ import '../widgets/general/navigationbar.dart';
import '../widgets/mobile/MobileBottomNav.dart';
class ForgotPassword extends StatelessWidget {
const ForgotPassword({Key key}) : super(key: key);
const ForgotPassword({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -28,15 +28,15 @@ class ForgotPassword extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileForgotPassword(),
tablet: DesktopForgotPassword(),
desktop: DesktopForgotPassword(),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileForgotPassword(),
tablet: (context) => DesktopForgotPassword(),
desktop: (context) => DesktopForgotPassword(),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+18 -16
View File
@@ -34,7 +34,7 @@ import '../widgets/mobile/mobile_navigation_drawer.dart';
class Home extends StatefulWidget {
final String title;
Home({Key key, this.title = ''}) : super(key: key);
Home({Key? key, this.title = ''}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -46,11 +46,11 @@ class Home extends StatefulWidget {
class HomeState extends State<Home> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
List<Gallery> galleries = [];
String content1Message;
Map<String, dynamic> content2;
String? content1Message;
Map<String, dynamic>? content2;
StreamSubscription _sub;
Uri _latestUri;
StreamSubscription? _sub;
Uri? _latestUri;
@override
Widget build(BuildContext context) {
@@ -74,8 +74,8 @@ class HomeState extends State<Home> {
appBar: MiniNavigationBar(),
drawer: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? MobileNavigationDrawer() : null,
body: DoubleBackToCloseAppWrapper(
child: ScreenTypeLayout(
mobile: SingleChildScrollView(
child: ScreenTypeLayout.builder(
mobile: (context) => SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -86,7 +86,7 @@ class HomeState extends State<Home> {
],
),
),
tablet: SingleChildScrollView(
tablet: (context) => SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -98,8 +98,8 @@ class HomeState extends State<Home> {
],
),
),
desktop: Scrollbar(
isAlwaysShown: true,
desktop: (context) => Scrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -115,10 +115,10 @@ class HomeState extends State<Home> {
),
),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
@@ -136,7 +136,7 @@ class HomeState extends State<Home> {
_loadData();
eventBus.on<OpenDrawer>().listen((event) {
if (mounted) {
_scaffoldKey.currentState.openDrawer();
_scaffoldKey.currentState?.openDrawer();
}
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
@@ -163,7 +163,9 @@ class HomeState extends State<Home> {
if (!mounted) return;
_latestUri = Uri.base;
print('uri base $_latestUri');
eventBus.fire(OnGotDeepLinkUri(_latestUri));
if (_latestUri != null) {
eventBus.fire(OnGotDeepLinkUri(_latestUri!));
}
} else {
// _sub = getUriLinksStream().listen((Uri uri) {
// print('_sub should get uri $uri');
+10 -10
View File
@@ -16,7 +16,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_igoshow_learn_more.dart';
class IGoShowLearnMore extends StatefulWidget {
const IGoShowLearnMore({Key key}) : super(key: key);
const IGoShowLearnMore({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -26,7 +26,7 @@ class IGoShowLearnMore extends StatefulWidget {
class IGoShowLearnMoreState extends State<IGoShowLearnMore> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
Map<String, dynamic> data;
Map<String, dynamic>? data;
@override
Widget build(BuildContext context) {
@@ -55,15 +55,15 @@ class IGoShowLearnMoreState extends State<IGoShowLearnMore> {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileiGoShowLearnMore(data),
tablet: DesktopiGoShowLearnMore(data),
desktop: DesktopiGoShowLearnMore(data),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileiGoShowLearnMore(data!),
tablet: (context) => DesktopiGoShowLearnMore(data!),
desktop: (context) => DesktopiGoShowLearnMore(data!),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+9 -11
View File
@@ -14,9 +14,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_login.dart';
class Login extends StatefulWidget {
final Key key;
const Login({this.key}) : super(key: key);
const Login({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -50,15 +48,15 @@ class LoginState extends State<Login> {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileLogin(),
tablet: DesktopLogin(),
desktop: DesktopLogin(),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileLogin(),
tablet: (context) => DesktopLogin(),
desktop: (context) => DesktopLogin(),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+9 -11
View File
@@ -15,9 +15,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_me.dart';
class Me extends StatefulWidget {
final Key key;
const Me({this.key}) : super(key: key);
const Me({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -52,16 +50,16 @@ class MeState extends State<Me> {
),
drawer: null,
body: DoubleBackToCloseAppWrapper(
child: ScreenTypeLayout(
mobile: MobileMe(),
tablet: DesktopMe(),
desktop: DesktopMe(),
child: ScreenTypeLayout.builder(
mobile: (context) => MobileMe(),
tablet: (context) => DesktopMe(),
desktop: (context) => DesktopMe(),
),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 3,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 3,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+30 -29
View File
@@ -16,7 +16,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_minipos_learn_more.dart';
class MiniPosLearnMore extends StatefulWidget {
const MiniPosLearnMore({Key key}) : super(key: key);
const MiniPosLearnMore({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -26,7 +26,7 @@ class MiniPosLearnMore extends StatefulWidget {
class MiniPosLearnMoreState extends State<MiniPosLearnMore> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
Map<String, dynamic> data;
Map<String, dynamic>? data;
@override
Widget build(BuildContext context) {
@@ -42,35 +42,36 @@ class MiniPosLearnMoreState extends State<MiniPosLearnMore> {
);
}
return WillPopScope(
child: ResponsiveBuilder(
builder: (context, sizingInformation) =>
Scaffold(
key: _scaffoldKey,
appBar: MiniNavigationBar(
title: S.of(context).minipos,
back: true,
breadCrumbs: [
BreadCrumb(S.of(context).minipos, null),
],
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileMiniPosLearnMore(data),
tablet: DesktopMiniPosLearnMore(data),
desktop: DesktopMiniPosLearnMore(data),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
),
return ResponsiveBuilder(
builder: (context, sizingInformation) => PopScope(
canPop: true,
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
// 可以在这里处理返回逻辑
},
child: Scaffold(
key: _scaffoldKey,
appBar: MiniNavigationBar(
title: S.of(context).minipos,
back: true,
breadCrumbs: [
BreadCrumb(S.of(context).minipos, null),
],
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
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;
},
);
}
+5 -6
View File
@@ -8,20 +8,19 @@ import '../widgets/desktop/desktop_my_addresses.dart';
import '../widgets/mobile/mobile_my_addresses.dart';
class MyAddresses extends StatelessWidget {
final Key key;
final int businessId;
const MyAddresses({this.key, int businessId}) :
const MyAddresses({Key? key, int? businessId}) :
businessId = businessId ?? Constants.BUSINESS_ID;
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileMyAddresses(businessId: businessId,),
tablet: DesktopMyAddresses(businessId: businessId,),
desktop: DesktopMyAddresses(businessId: businessId,),
ScreenTypeLayout.builder(
mobile: (context) => MobileMyAddresses(businessId: businessId,),
tablet: (context) => DesktopMyAddresses(businessId: businessId,),
desktop: (context) => DesktopMyAddresses(businessId: businessId,),
),
);
}
+4 -4
View File
@@ -35,10 +35,10 @@ class MyCards extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileMyCards(),
tablet: DesktopMyCards(),
desktop: DesktopMyCards(),
ScreenTypeLayout.builder(
mobile: (context) => MobileMyCards(),
tablet: (context) => DesktopMyCards(),
desktop: (context) => DesktopMyCards(),
),
);
}
+5 -6
View File
@@ -8,20 +8,19 @@ import '../widgets/desktop/desktop_my_support.dart';
import '../widgets/mobile/mobile_my_support.dart';
class MySupport extends StatelessWidget {
final Key key;
final int businessId;
const MySupport({this.key, int businessId}) :
const MySupport({Key? key, int? businessId}) :
businessId = businessId ?? Constants.BUSINESS_ID;
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileMySupport(businessId: businessId,),
tablet: DesktopMySupport(businessId: businessId,),
desktop: DesktopMySupport(businessId: businessId,),
ScreenTypeLayout.builder(
mobile: (context) => MobileMySupport(businessId: businessId,),
tablet: (context) => DesktopMySupport(businessId: businessId,),
desktop: (context) => DesktopMySupport(businessId: businessId,),
),
);
}
+7 -8
View File
@@ -7,25 +7,24 @@ import '../widgets/desktop/desktop_new_address.dart';
import '../widgets/mobile/mobile_new_address.dart';
class NewAddress extends StatelessWidget {
final Key key;
final LocatedAddress locatedAddress;
final int businessId;
const NewAddress({this.key, this.locatedAddress, this.businessId}) : super(key: key);
final LocatedAddress? locatedAddress;
final int? businessId;
const NewAddress({Key? key, this.locatedAddress, this.businessId}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileNewAddress(
ScreenTypeLayout.builder(
mobile: (context) => MobileNewAddress(
locatedAddress: locatedAddress,
businessId: businessId,
),
tablet: DesktopNewAddress(
tablet: (context) => DesktopNewAddress(
locatedAddress: locatedAddress,
businessId: businessId,
),
desktop: DesktopNewAddress(
desktop: (context) => DesktopNewAddress(
locatedAddress: locatedAddress,
businessId: businessId,
),
+5 -5
View File
@@ -10,7 +10,7 @@ import '../widgets/mobile/mobile_new_comment.dart';
class NewComment extends StatelessWidget {
final int orderId;
const NewComment(this.orderId, {Key key}) :
const NewComment(this.orderId, {Key? key}) :
super(key: key);
@@ -20,10 +20,10 @@ class NewComment extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileNewComment(orderId,),
tablet: DesktopNewComment(orderId,),
desktop: DesktopNewComment(orderId,),
ScreenTypeLayout.builder(
mobile: (context) => MobileNewComment(orderId,),
tablet: (context) => DesktopNewComment(orderId,),
desktop: (context) => DesktopNewComment(orderId,),
),
);
}
+10 -11
View File
@@ -14,10 +14,9 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_new_ticket.dart';
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;
@override
@@ -52,15 +51,15 @@ class NewTicketState extends State<NewTicket> {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileNewTicket(businessId: widget.businessId,),
tablet: DesktopNewTicket(businessId: widget.businessId,),
desktop: DesktopNewTicket(businessId: widget.businessId,),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileNewTicket(businessId: widget.businessId!,),
tablet: (context) => DesktopNewTicket(businessId: widget.businessId!,),
desktop: (context) => DesktopNewTicket(businessId: widget.businessId!,),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+9 -9
View File
@@ -12,7 +12,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_new_user.dart';
class NewUser extends StatelessWidget {
const NewUser({Key key}) : super(key: key);
const NewUser({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -28,15 +28,15 @@ class NewUser extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileNewUser(),
tablet: DesktopNewUser(),
desktop: DesktopNewUser(),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileNewUser(),
tablet: (context) => DesktopNewUser(),
desktop: (context) => DesktopNewUser(),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+5 -5
View File
@@ -11,7 +11,7 @@ class OrderDetail extends StatelessWidget {
final int orderId;
final bool fromOrders;
const OrderDetail(this.orderId, {this.fromOrders = false, Key key}) :
const OrderDetail(this.orderId, {this.fromOrders = false, Key? key}) :
super(key: key);
@override
@@ -20,10 +20,10 @@ class OrderDetail extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileOrderDetail(orderId, fromOrders: fromOrders,),
tablet: DesktopOrderDetail(orderId, fromOrders: fromOrders,),
desktop: DesktopOrderDetail(orderId, fromOrders: fromOrders,),
ScreenTypeLayout.builder(
mobile: (context) => MobileOrderDetail(orderId, fromOrders: fromOrders,),
tablet: (context) => DesktopOrderDetail(orderId, fromOrders: fromOrders,),
desktop: (context) => DesktopOrderDetail(orderId, fromOrders: fromOrders,),
),
);
}
+5 -5
View File
@@ -10,7 +10,7 @@ import '../widgets/mobile/mobile_pay_now.dart';
class PayNow extends StatelessWidget {
final int orderId;
const PayNow(this.orderId, {Key key}) :
const PayNow(this.orderId, {Key? key}) :
super(key: key);
@@ -20,10 +20,10 @@ class PayNow extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobilePayNow(orderId,),
tablet: DesktopPayNow(orderId,),
desktop: DesktopPayNow(orderId,),
ScreenTypeLayout.builder(
mobile: (context) => MobilePayNow(orderId,),
tablet: (context) => DesktopPayNow(orderId,),
desktop: (context) => DesktopPayNow(orderId,),
),
);
}
+7 -7
View File
@@ -13,9 +13,9 @@ import '../utils/utils.dart';
class PlainPage extends StatefulWidget {
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);
@override
@@ -23,7 +23,7 @@ class PlainPage extends StatefulWidget {
}
class PlainPageState extends State<PlainPage> {
Blog blog;
Blog? blog;
@override
Widget build(BuildContext context) {
@@ -43,10 +43,10 @@ class PlainPageState extends State<PlainPage> {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobilePlainPage(blog),
tablet: DesktopPlainPage(blog),
desktop: DesktopPlainPage(blog),
ScreenTypeLayout.builder(
mobile: (context) => MobilePlainPage(blog!),
tablet: (context) => DesktopPlainPage(blog!),
desktop: (context) => DesktopPlainPage(blog!),
),
);
}
+5 -5
View File
@@ -13,7 +13,7 @@ class ProductDetailPage extends StatelessWidget {
final Business business;
final Product product;
const ProductDetailPage({this.business, this.product, Key key}) :
const ProductDetailPage({required this.business, required this.product, Key? key}) :
super(key: key);
@@ -23,10 +23,10 @@ class ProductDetailPage extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileProductDetailPage(business: business, product: product,),
tablet: DesktopProductDetailPage(business: business, product: product,),
desktop: DesktopProductDetailPage(business: business, product: product,),
ScreenTypeLayout.builder(
mobile: (context) => MobileProductDetailPage(business: business, product: product,),
tablet: (context) => DesktopProductDetailPage(business: business, product: product,),
desktop: (context) => DesktopProductDetailPage(business: business, product: product,),
),
);
}
+5 -5
View File
@@ -9,16 +9,16 @@ import '../widgets/mobile/product_search.dart' as mobile;
class ProductSearch extends StatelessWidget {
final Business business;
const ProductSearch(this.business, {Key key}) : super(key: key);
const ProductSearch(this.business, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: mobile.ProductSearch(business),
tablet: desktop.ProductSearch(business),
desktop: desktop.ProductSearch(business),
ScreenTypeLayout.builder(
mobile: (context) => mobile.ProductSearch(business),
tablet: (context) => desktop.ProductSearch(business),
desktop: (context) => desktop.ProductSearch(business),
),
);
}
+5 -5
View File
@@ -12,7 +12,7 @@ import '../widgets/mobile/mobile_renew_license.dart';
class RenewLicense extends StatefulWidget {
const RenewLicense({Key key}) :
const RenewLicense({Key? key}) :
super(key: key);
@override
@@ -28,10 +28,10 @@ class RenewLicenseState extends State<RenewLicense> {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileRenewLicense(Constants.BUSINESS_ID),
tablet: DesktopRenewLicense(Constants.BUSINESS_ID),
desktop: DesktopRenewLicense(Constants.BUSINESS_ID),
ScreenTypeLayout.builder(
mobile: (context) => MobileRenewLicense(Constants.BUSINESS_ID),
tablet: (context) => DesktopRenewLicense(Constants.BUSINESS_ID),
desktop: (context) => DesktopRenewLicense(Constants.BUSINESS_ID),
),
);
}
+6 -6
View File
@@ -17,7 +17,7 @@ import '../widgets/mobile/mobile_renew_license.dart';
class RenewMiniOffice extends StatefulWidget {
final int gid;
const RenewMiniOffice(this.gid, {Key key}) :
const RenewMiniOffice(this.gid, {Key? key}) :
super(key: key);
@override
@@ -25,7 +25,7 @@ class RenewMiniOffice extends StatefulWidget {
}
class RenewMiniOfficeState extends State<RenewMiniOffice> {
Map<String, dynamic> data;
Map<String, dynamic>? data;
@override
Widget build(BuildContext context) {
@@ -45,10 +45,10 @@ class RenewMiniOfficeState extends State<RenewMiniOffice> {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileRenewMiniOffice(data),
tablet: DesktopRenewMiniOffice(data),
desktop: DesktopRenewMiniOffice(data),
ScreenTypeLayout.builder(
mobile: (context) => MobileRenewMiniOffice(data!),
tablet: (context) => DesktopRenewMiniOffice(data!),
desktop: (context) => DesktopRenewMiniOffice(data!),
),
);
}
+9 -9
View File
@@ -14,7 +14,7 @@ import '../widgets/mobile/mobile_reset_password.dart';
class ResetPassword extends StatelessWidget {
final String mobile;
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
Widget build(BuildContext context) {
@@ -30,15 +30,15 @@ class ResetPassword extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileResetPassword(mobile, code: code,),
tablet: DesktopResetPassword(mobile, code: code,),
desktop: DesktopResetPassword(mobile, code: code,),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileResetPassword(mobile, code: code,),
tablet: (context) => DesktopResetPassword(mobile, code: code,),
desktop: (context) => DesktopResetPassword(mobile, code: code,),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+5 -6
View File
@@ -5,18 +5,17 @@ import '../widgets/mobile/mobile_search_place.dart';
import 'package:responsive_builder/responsive_builder.dart';
class SearchPlace extends StatelessWidget {
final Key key;
final int businessId;
const SearchPlace(this.businessId, {this.key}) : super(key: key);
const SearchPlace(this.businessId, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileSearchPlace(businessId),
tablet: DesktopSearchPlace(businessId),
desktop: DesktopSearchPlace(businessId),
ScreenTypeLayout.builder(
mobile: (context) => MobileSearchPlace(businessId),
tablet: (context) => DesktopSearchPlace(businessId),
desktop: (context) => DesktopSearchPlace(businessId),
),
);
}
+9 -9
View File
@@ -14,7 +14,7 @@ import '../widgets/mobile/mobile_set_password.dart';
class SetPassword extends StatelessWidget {
final String mobile;
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
Widget build(BuildContext context) {
@@ -30,15 +30,15 @@ class SetPassword extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileSetPassword(mobile, code: code,),
tablet: DesktopSetPassword(mobile, code: code,),
desktop: DesktopSetPassword(mobile, code: code,),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileSetPassword(mobile, code: code,),
tablet: (context) => DesktopSetPassword(mobile, code: code,),
desktop: (context) => DesktopSetPassword(mobile, code: code,),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+7 -7
View File
@@ -8,9 +8,9 @@ import '../widgets/desktop/shop.dart' as desktop;
import '../widgets/mobile/shop.dart' as mobile;
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
State<StatefulWidget> createState() => ShopState();
@@ -20,17 +20,17 @@ class ShopState extends State<Shop> {
@override
Widget build(BuildContext context) {
int _businessId =
widget.businessId == null ? Utils.getBusinessId() : widget.businessId;
widget.businessId == null ? Utils.getBusinessId() : widget.businessId!;
return ResponsiveBuilder(
builder: (context, sizingInformation) => ScreenTypeLayout(
mobile: mobile.Shop(
builder: (context, sizingInformation) => ScreenTypeLayout.builder(
mobile: (context) => mobile.Shop(
businessId: _businessId,
),
tablet: desktop.Shop(
tablet: (context) => desktop.Shop(
businessId: _businessId,
),
desktop: desktop.Shop(
desktop: (context) => desktop.Shop(
businessId: _businessId,
),
),
+9 -9
View File
@@ -15,7 +15,7 @@ import '../widgets/mobile/mobile_store_product_search.dart';
class StoreProductSearch extends StatelessWidget {
final Business business;
const StoreProductSearch(this.business, {Key key}) : super(key: key);
const StoreProductSearch(this.business, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -31,15 +31,15 @@ class StoreProductSearch extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileStoreProductSearch(business,),
tablet: DesktopStoreProductSearch(business),
desktop: DesktopStoreProductSearch(business),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileStoreProductSearch(business,),
tablet: (context) => DesktopStoreProductSearch(business),
desktop: (context) => DesktopStoreProductSearch(business),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 1,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 1,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+5 -5
View File
@@ -13,7 +13,7 @@ import '../widgets/mobile/mobile_stripe_pay_web.dart';
class StripePayWeb extends StatelessWidget {
final Order order;
final PaymentPlatform paymentPlatform;
final StripePaymentMethod stripePaymentMethod;
final StripePaymentMethod? stripePaymentMethod;
const StripePayWeb(this.order, this.paymentPlatform, {this.stripePaymentMethod});
@@ -23,10 +23,10 @@ class StripePayWeb extends StatelessWidget {
return ResponsiveBuilder(
builder: (context, sizingInformation) =>
ScreenTypeLayout(
mobile: MobileStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
tablet: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
desktop: DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
ScreenTypeLayout.builder(
mobile: (context) => MobileStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
tablet: (context) => DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
desktop: (context) => DesktopStripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,),
),
);
}
+9 -9
View File
@@ -13,7 +13,7 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_user_profile.dart';
class UserProfile extends StatelessWidget {
const UserProfile({Key key}) : super(key: key);
const UserProfile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -29,15 +29,15 @@ class UserProfile extends StatelessWidget {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileUserProfile(),
tablet: DesktopUserProfile(),
desktop: DesktopUserProfile(),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileUserProfile(),
tablet: (context) => DesktopUserProfile(),
desktop: (context) => DesktopUserProfile(),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+9 -10
View File
@@ -14,10 +14,9 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_view_blog.dart';
class ViewBlog extends StatefulWidget {
final Key key;
final int bid;
const ViewBlog(this.bid, {this.key}) : super(key: key);
const ViewBlog(this.bid, {Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -51,15 +50,15 @@ class ViewBlogState extends State<ViewBlog> {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileViewBlog(widget.bid),
tablet: DesktopViewBlog(widget.bid),
desktop: DesktopViewBlog(widget.bid),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileViewBlog(widget.bid),
tablet: (context) => DesktopViewBlog(widget.bid),
desktop: (context) => DesktopViewBlog(widget.bid),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+9 -10
View File
@@ -14,10 +14,9 @@ import '../widgets/mobile/MobileBottomNav.dart';
import '../widgets/mobile/mobile_view_ticket.dart';
class ViewTicket extends StatefulWidget {
final Key key;
final int ticketId;
const ViewTicket(this.ticketId, {this.key}) : super(key: key);
const ViewTicket(this.ticketId, {Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -51,15 +50,15 @@ class ViewTicketState extends State<ViewTicket> {
breadCrumbHeight: sizingInformation.deviceScreenType == DeviceScreenType.mobile ? null : Constants.BREADCRUMB_HEIGHT,
),
drawer: null,
body: ScreenTypeLayout(
mobile: MobileViewTicket(widget.ticketId),
tablet: DesktopViewTicket(widget.ticketId),
desktop: DesktopViewTicket(widget.ticketId),
body: ScreenTypeLayout.builder(
mobile: (context) => MobileViewTicket(widget.ticketId),
tablet: (context) => DesktopViewTicket(widget.ticketId),
desktop: (context) => DesktopViewTicket(widget.ticketId),
),
bottomNavigationBar: ScreenTypeLayout(
mobile: MobileBottomNav(currentIndex: 0,),
tablet: BottomNav(),
desktop: BottomNav(),
bottomNavigationBar: ScreenTypeLayout.builder(
mobile: (context) => MobileBottomNav(currentIndex: 0,),
tablet: (context) => BottomNav(),
desktop: (context) => BottomNav(),
),
),
);
+58 -58
View File
@@ -44,169 +44,169 @@ class Routes {
static void configure() {
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,);
}),
transitionType: TransitionType.fadeIn
);
router.define('/download', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Download();
}),
transitionType: TransitionType.inFromRight
);
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();
}),
transitionType: TransitionType.inFromRight
);
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();
}),
transitionType: TransitionType.inFromRight
);
router.define('/login', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Login();
}),
transitionType: TransitionType.inFromRight
);
router.define('/me', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Me();
}),
transitionType: TransitionType.inFromRight
);
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();
}),
transitionType: TransitionType.inFromRight
);
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();
}),
transitionType: TransitionType.inFromRight
);
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();
}),
transitionType: TransitionType.inFromRight
);
router.define('/set-password/:mobile/:code', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return SetPassword(params['mobile'][0], code: params['code'][0]);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return SetPassword(params['mobile']![0], code: params['code']![0]);
}
));
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();
}
));
router.define('/reset-password/:mobile/:code', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return ResetPassword(params['mobile'][0], code: params['code'][0]);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return ResetPassword(params['mobile']![0], code: params['code']![0]);
}
));
router.define('/change-mobile-email/:ismobile', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
if (params['ismobile'][0] == '1') {
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
if (params['ismobile']![0] == '1') {
return ChangeMobileOrEmail(true);
}
return ChangeMobileOrEmail(false);
}
));
router.define('/my-addresses/:business_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return MyAddresses(businessId: int.parse(params['business_id'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return MyAddresses(businessId: int.parse(params['business_id']![0]),);
}
));
router.define('/my-support/:business_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return MySupport(businessId: int.parse(params['business_id'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return MySupport(businessId: int.parse(params['business_id']![0]),);
}
));
router.define('/new-ticket/:business_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return NewTicket(businessId: int.parse(params['business_id'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return NewTicket(businessId: int.parse(params['business_id']![0]),);
}
));
router.define('/search-place/:business_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return SearchPlace(int.parse(params['business_id'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return SearchPlace(int.parse(params['business_id']![0]));
}
));
router.define('/view-ticket/:ticket_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return ViewTicket(int.parse(params['ticket_id'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return ViewTicket(int.parse(params['ticket_id']![0]),);
}
));
router.define('/blog/:business_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return Blog(businessId: int.parse(params['business_id'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Blog(businessId: int.parse(params['business_id']![0]),);
}
));
router.define('/view-blog/:bid', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return ViewBlog(int.parse(params['bid'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return ViewBlog(int.parse(params['bid']![0]),);
}
));
router.define('/shop/:business_id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return Shop(businessId: int.parse(params['business_id'][0]),);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Shop(businessId: int.parse(params['business_id']![0]),);
}
));
router.define('/shop', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Shop();
}
));
// 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();
// }
// ));
router.define('/checkout/:id', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return Checkout(int.parse(params['id'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Checkout(int.parse(params['id']![0]));
}),
);
router.define('/paynow/:orderId', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return PayNow(int.parse(params['orderId'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return PayNow(int.parse(params['orderId']![0]));
}),
);
router.define('/orders', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Orders();
}
));
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();
}
));
router.define('/orderdetail/:orderId', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return OrderDetail(int.parse(params['orderId'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return OrderDetail(int.parse(params['orderId']![0]));
}),
);
router.define('/new-comment/:orderId', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return NewComment(int.parse(params['orderId'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return NewComment(int.parse(params['orderId']![0]));
}),
);
router.define('/coupons/:contactId', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return Coupons(int.parse(params['contactId'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return Coupons(int.parse(params['contactId']![0]));
}),
);
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(
'service-policy',
// businessId: Constants.BUSINESS_ID,
@@ -215,7 +215,7 @@ class Routes {
}),
);
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-policy',
// businessId: Constants.BUSINESS_ID,
@@ -224,7 +224,7 @@ class Routes {
}),
);
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(
'privacy-policy',
// businessId: Constants.BUSINESS_ID,
@@ -233,7 +233,7 @@ class Routes {
}),
);
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(
'end-user-license-agreement',
// businessId: Constants.BUSINESS_ID,
@@ -242,7 +242,7 @@ class Routes {
}),
);
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(
'about-us',
// businessId: Constants.BUSINESS_ID,
@@ -251,29 +251,29 @@ class Routes {
}),
);
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(
businessId: Constants.BUSINESS_ID,
);
}),
);
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();
}
));
router.define('/renew-minioffice/:gid', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return RenewMiniOffice(int.parse(params['gid'][0]));
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return RenewMiniOffice(int.parse(params['gid']![0]));
}
));
router.define('/buy-service/:gid/:servicename', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return BuyService(int.parse(params['gid'][0]), params['servicename'][0]);
handlerFunc: (BuildContext? context, Map<String, List<String>> params) {
return BuyService(int.parse(params['gid']![0]), params['servicename']![0]);
}
));
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();
}
));
+3 -3
View File
@@ -10,17 +10,17 @@ class UpdateContext {
}
class UpdateLocale {
final Locale locale;
final Locale? locale;
UpdateLocale(this.locale);
}
class UpdateCurrentUser {
final User user;
final User? user;
UpdateCurrentUser(this.user);
}
class UpdateRedirectRoute {
final String route;
final String? route;
UpdateRedirectRoute(this.route);
}
+3 -3
View File
@@ -4,10 +4,10 @@ import 'package:redux/redux.dart';
import '../actions.dart';
final cartInfoReducer = combineReducers<List<CartInfo>>([
TypedReducer<List<CartInfo>, UpdateCartInfo>(_updateCartInfo)
final cartInfoReducer = combineReducers<List<CartInfo>?>([
TypedReducer<List<CartInfo>?, UpdateCartInfo>(_updateCartInfo)
]);
List<CartInfo> _updateCartInfo(List<CartInfo> cartInfos, action) {
List<CartInfo> _updateCartInfo(List<CartInfo>? cartInfos, action) {
return action.cartInfos;
}
+3 -3
View File
@@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/store/actions.dart';
import 'package:redux/redux.dart';
final contextReducer = combineReducers<BuildContext>([
TypedReducer<BuildContext, UpdateContext>(_updateContext)
final contextReducer = combineReducers<BuildContext?>([
TypedReducer<BuildContext?, UpdateContext>(_updateContext)
]);
BuildContext _updateContext(BuildContext context, action) {
BuildContext _updateContext(BuildContext? context, action) {
return action.context;
}
+3 -3
View File
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
import '../actions.dart';
final deviceIdReducer = combineReducers<String>([
TypedReducer<String, UpdateDeviceId>(_updateDeviceId)
final deviceIdReducer = combineReducers<String?>([
TypedReducer<String?, UpdateDeviceId>(_updateDeviceId)
]);
String _updateDeviceId(String deviceId, action) {
String _updateDeviceId(String? deviceId, action) {
return action.deviceId;
}
+3 -3
View File
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
import '../actions.dart';
final fcmtokenReducer = combineReducers<String>([
TypedReducer<String, UpdateFcmToken>(_updateFcmToken)
final fcmtokenReducer = combineReducers<String?>([
TypedReducer<String?, UpdateFcmToken>(_updateFcmToken)
]);
String _updateFcmToken(String token, action) {
String _updateFcmToken(String? token, action) {
return action.token;
}
+3 -3
View File
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
import '../actions.dart';
final lastVisitReducer = combineReducers<List<int>>([
TypedReducer<List<int>, UpdateLastVisit>(_updateLastVisit)
final lastVisitReducer = combineReducers<List<int>?>([
TypedReducer<List<int>?, UpdateLastVisit>(_updateLastVisit)
]);
List<int> _updateLastVisit(List<int> lastVisit, action) {
List<int> _updateLastVisit(List<int>? lastVisit, action) {
return action.lastVisit;
}
+3 -3
View File
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_wisetronic/store/actions.dart';
import 'package:redux/redux.dart';
final localeReducer = combineReducers<Locale>([
TypedReducer<Locale, UpdateLocale>(_updateLocale)
final localeReducer = combineReducers<Locale?>([
TypedReducer<Locale?, UpdateLocale>(_updateLocale)
]);
Locale _updateLocale(Locale locale, action) {
Locale _updateLocale(Locale? locale, action) {
return action.locale;
}
@@ -3,10 +3,10 @@ import 'package:redux/redux.dart';
import '../../models/located_address.dart';
import '../actions.dart';
final locatedAddressReducer = combineReducers<LocatedAddress>([
TypedReducer<LocatedAddress, UpdateLocatedAddress>(_updateLocatedAddress)
final locatedAddressReducer = combineReducers<LocatedAddress?>([
TypedReducer<LocatedAddress?, UpdateLocatedAddress>(_updateLocatedAddress)
]);
LocatedAddress _updateLocatedAddress(LocatedAddress locatedAddress, action) {
LocatedAddress _updateLocatedAddress(LocatedAddress? locatedAddress, action) {
return action.locatedAddress;
}
@@ -2,10 +2,10 @@
import 'package:redux/redux.dart';
import '../actions.dart';
final redirectRouteReducer = combineReducers<String>([
TypedReducer<String, UpdateRedirectRoute>(_updateRedirectRoute)
final redirectRouteReducer = combineReducers<String?>([
TypedReducer<String?, UpdateRedirectRoute>(_updateRedirectRoute)
]);
String _updateRedirectRoute(String route, action) {
String? _updateRedirectRoute(String? route, action) {
return action.route;
}
+3 -3
View File
@@ -2,10 +2,10 @@ import 'package:redux/redux.dart';
import '../actions.dart';
final tableNumberReducer = combineReducers<String>([
TypedReducer<String, UpdateTableNumber>(_updateTableNumber)
final tableNumberReducer = combineReducers<String?>([
TypedReducer<String?, UpdateTableNumber>(_updateTableNumber)
]);
String _updateTableNumber(String tableNumber, action) {
String _updateTableNumber(String? tableNumber, action) {
return action.tableNumber;
}
+3 -3
View File
@@ -4,10 +4,10 @@ import 'package:flutter_wisetronic/models/user.dart';
import '../actions.dart';
final userReducer = combineReducers<User>([
TypedReducer<User, UpdateCurrentUser>(_updateCurrentUser)
final userReducer = combineReducers<User?>([
TypedReducer<User?, UpdateCurrentUser>(_updateCurrentUser)
]);
User _updateCurrentUser(User user, action) {
User? _updateCurrentUser(User? user, action) {
return action.user;
}
+12 -12
View File
@@ -6,16 +6,16 @@ import '../../models/user.dart';
@immutable
class AppState {
final BuildContext context;
final Locale locale;
final User user;
final String redirectRoute;
final List<CartInfo> cartInfos;
final LocatedAddress locatedAddress;
final String fcmToken;
final List<int> lastVisit;
final String deviceId;
final String tableNumber;
final BuildContext? context;
final Locale? locale;
final User? user;
final String? redirectRoute;
final List<CartInfo>? cartInfos;
final LocatedAddress? locatedAddress;
final String? fcmToken;
final List<int>? lastVisit;
final String? deviceId;
final String? tableNumber;
AppState({this.context, this.locale, this.user, this.redirectRoute,
this.cartInfos, this.locatedAddress, this.fcmToken, this.lastVisit,
@@ -24,8 +24,8 @@ class AppState {
factory AppState.init() => AppState();
AppState copyWith({
BuildContext context,
Locale locale}) {
BuildContext? context,
Locale? locale}) {
return AppState(
context: context ?? this.context,
locale: locale ?? this.locale,
+6 -6
View File
@@ -21,9 +21,9 @@ class DoubleBackToCloseApp extends StatefulWidget {
/// 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,
Key? key,
required this.snackBar,
required this.child,
}) : assert(snackBar != null),
assert(child != null),
super(key: key);
@@ -34,7 +34,7 @@ class DoubleBackToCloseApp extends StatefulWidget {
class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
/// The last time the user tapped Android's back-button.
DateTime _lastTimeBackButtonWasTapped;
DateTime? _lastTimeBackButtonWasTapped;
/// Returns whether the current platform is Android.
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
@@ -50,7 +50,7 @@ class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
bool get _isSnackBarVisible =>
(_lastTimeBackButtonWasTapped != null) &&
(widget.snackBar.duration >
DateTime.now().difference(_lastTimeBackButtonWasTapped));
DateTime.now().difference(_lastTimeBackButtonWasTapped!));
/// Returns whether the next back navigation of this route will be handled
/// internally.
@@ -59,7 +59,7 @@ class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
/// 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;
ModalRoute.of(context)!.willHandlePopInternally;
@override
Widget build(BuildContext context) {
+3 -3
View File
@@ -2,9 +2,9 @@
import 'package:flutter/material.dart';
class IFrameWeb extends StatefulWidget {
final String width;
final String height;
final String src;
final String? width;
final String? height;
final String? src;
const IFrameWeb({this.width, this.height, this.src});
+59 -60
View File
@@ -7,14 +7,12 @@ import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import '../store/store.dart';
import 'utils.dart';
import 'package:hive/hive.dart';
import 'package:universal_io/io.dart';
import '../constants.dart';
typedef void PostCallback(Response response);
String generateSignature(String string, {String key}) {
String generateSignature(String string, {String? key}) {
if (key == null) {
key = Constants.API_SECRET;
}
@@ -37,17 +35,17 @@ class HttpUtil {
'Http-Contact-Authorization': '',
'Http-Device-Type': Utils.getOs(checkWeb: true),
'Http-Api-Branch': 'flutter',
'Http-Language-Code': store.state.locale.languageCode,
'Http-Country-Code': store.state.locale.countryCode,
'Http-Language-Code': (store == null || store.state == null || store.state.locale == null) ? 'en' : store.state.locale!.languageCode,
'Http-Country-Code': (store == null || store.state == null || store.state.locale == null) ? 'US' : store.state.locale!.countryCode!,
};
static Future<dynamic> httpGet(String url,
{
Map<String, dynamic> queryParameters,
Map<String, dynamic>? queryParameters,
int businessId = 0,
Function(int, int) receiveProgress,
Function(int, int)? receiveProgress,
bool returnError = false,
Map<String, String> additionalHeaders,
Map<String, String>? additionalHeaders,
}) async {
Map<String, dynamic> localHeaders = json.decode(json.encode(headers));
if (additionalHeaders != null) {
@@ -59,8 +57,7 @@ class HttpUtil {
}
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
Box box = await Utils.getBox();
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
localHeaders['Http-App-Key'] = platformName;
localHeaders['Http-Device-Type'] = platformName;
String requestUrl = Constants.BASE_API_URL + url;
@@ -69,7 +66,9 @@ class HttpUtil {
requestUrl = url;
}
Utils.jsonPrettyPrint(queryParameters);
if (queryParameters != null) {
Utils.jsonPrettyPrint(queryParameters!);
}
Dio dio = Dio();
try {
@@ -84,9 +83,9 @@ class HttpUtil {
// print('response data: ${response.data}');
// Utils.jsonPrettyPrint(response.data);
int statusCode = response.statusCode;
int statusCode = response.statusCode!;
return response.data;
} on DioError catch(e) {
} on DioException catch(e) {
print('HttpGet failed, request: $requestUrl, headers: $localHeaders, error ${e.response}');
if (returnError) {
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,
bool isFormData = false,
Map<String, String> additionalHeaders,
Map<String, dynamic> body,
FormData formData,
Function(int, int) sendProgress,
Function(int, int) receiveProgress,
Map<String, String>? additionalHeaders,
Map<String, dynamic>? body,
FormData? formData,
Function(int, int)? sendProgress,
Function(int, int)? receiveProgress,
bool returnError = false,
}) async {
@@ -116,8 +115,7 @@ class HttpUtil {
localHeaders['Http-Signature'] = generateSignature(url);
}
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
Box box = await Utils.getBox();
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
localHeaders['Http-App-Key'] = platformName;
localHeaders['Http-Device-Type'] = platformName;
@@ -138,7 +136,7 @@ class HttpUtil {
Response response = await dio.post(
requestUrl,
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(
headers: localHeaders,
contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType,
@@ -149,17 +147,17 @@ class HttpUtil {
// Utils.jsonPrettyPrint(response.data);
int statusCode = response.statusCode;
int statusCode = response.statusCode!;
if (callback != null) {
callback(response);
}
return response.data;
} on DioError catch(e, stacktrace) {
} on DioException catch(e, stacktrace) {
if (e.response != null) {
try {
Utils.jsonPrettyPrint(e.response.data);
Utils.jsonPrettyPrint(e.response?.data);
} catch (err) {
print(e.response.data);
print(e.response?.data);
}
}
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,
Map<String, String> additionalHeaders,
Map<String, dynamic> body,
Function(int, int) sendProgress,
Function(int, int) receiveProgress,
Map<String, String>? additionalHeaders,
Map<String, dynamic>? body,
Function(int, int)? sendProgress,
Function(int, int)? receiveProgress,
bool returnError = false,
}) async {
@@ -190,8 +188,7 @@ class HttpUtil {
}
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
Box box = await Utils.getBox();
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
localHeaders['Http-App-Key'] = platformName;
localHeaders['Http-Device-Type'] = platformName;
@@ -218,14 +215,14 @@ class HttpUtil {
// Utils.jsonPrettyPrint(response.data);
int statusCode = response.statusCode;
int statusCode = response.statusCode!;
if (callback != null) {
callback(response);
}
return response.data;
} on DioError catch(e) {
} on DioException catch(e) {
if (e.response != null) {
Utils.jsonPrettyPrint(e.response.data);
Utils.jsonPrettyPrint(e.response!.data);
}
if (returnError) {
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,
Map<String, String> additionalHeaders,
Map<String, dynamic> body,
Function(int, int) sendProgress,
Function(int, int) receiveProgress,
Map<String, String>? additionalHeaders,
Map<String, dynamic>? body,
Function(int, int)? sendProgress,
Function(int, int)? receiveProgress,
bool returnError = false,
}) async {
@@ -255,8 +252,7 @@ class HttpUtil {
}
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
Box box = await Utils.getBox();
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
localHeaders['Http-App-Key'] = platformName;
localHeaders['Http-Device-Type'] = platformName;
@@ -268,7 +264,9 @@ class HttpUtil {
requestUrl = url;
}
Utils.jsonPrettyPrint(body);
if (body != null) {
Utils.jsonPrettyPrint(body!);
}
Dio dio = Dio();
try {
@@ -285,14 +283,14 @@ class HttpUtil {
// Utils.jsonPrettyPrint(response.data);
int statusCode = response.statusCode;
int statusCode = response.statusCode!;
if (callback != null) {
callback(response);
}
return response.data;
} on DioError catch(e) {
} on DioException catch(e) {
if (e.response != null) {
Utils.jsonPrettyPrint(e.response.data);
Utils.jsonPrettyPrint(e.response!.data);
}
if (returnError) {
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,
Map<String, String> additionalHeaders,
Map<String, dynamic> body,
Map<String, String>? additionalHeaders,
Map<String, dynamic>? body,
bool returnError = false,
}) async {
@@ -320,8 +318,7 @@ class HttpUtil {
}
localHeaders['Http-Business-Id'] = businessId == 0 ? '${Constants.BUSINESS_ID}' : businessId.toString();
Box box = await Utils.getBox();
localHeaders['Http-Contact-Authorization'] = box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
localHeaders['Http-Contact-Authorization'] = Utils.prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
localHeaders['Http-App-Key'] = platformName;
localHeaders['Http-Device-Type'] = platformName;
@@ -333,7 +330,9 @@ class HttpUtil {
requestUrl = url;
}
Utils.jsonPrettyPrint(body);
if (body != null) {
Utils.jsonPrettyPrint(body);
}
Dio dio = Dio();
try {
@@ -348,14 +347,14 @@ class HttpUtil {
// Utils.jsonPrettyPrint(response.data);
int statusCode = response.statusCode;
int statusCode = response.statusCode!;
if (callback != null) {
callback(response);
}
return response.data;
} on DioError catch(e) {
} on DioException catch(e) {
if (e.response != null) {
Utils.jsonPrettyPrint(e.response.data);
Utils.jsonPrettyPrint(e.response!.data);
}
if(returnError) {
return e;
+140
View File
@@ -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];
}
+164
View File
@@ -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();
}
}
+6 -6
View File
@@ -14,7 +14,7 @@ class ShopScrollController extends ScrollController {
this.coordinator, {
double initialScrollOffset = 0.0,
this.keepScrollOffset = true,
this.debugLabel,
required this.debugLabel,
}) : assert(initialScrollOffset != null),
assert(keepScrollOffset != null),
_initialScrollOffset = initialScrollOffset;
@@ -61,7 +61,7 @@ class ShopScrollController extends ScrollController {
'ScrollController not attached to any scroll views.');
assert(_positions.length == 1,
'ScrollController attached to multiple scroll views.');
return _positions.single;
return _positions.single as ShopScrollPosition;
}
/// 可滚动小部件的当前滚动偏移量
@@ -83,12 +83,12 @@ class ShopScrollController extends ScrollController {
/// 持续时间不能为零。 要在没有动画的情况下跳至特定值,请使用[jumpTo]。
Future<void> animateTo(
double offset, {
@required Duration duration,
@required Curve curve,
required Duration duration,
required Curve curve,
}) {
assert(_positions.isNotEmpty,
'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)
animations[i] =
_positions[i].animateTo(offset, duration: duration, curve: curve);
@@ -149,7 +149,7 @@ class ShopScrollController extends ScrollController {
/// 则它将是前一个实例。 当环境已更改并且[Scrollable]需要重新创建[ScrollPosition]
/// 对象时,将使用此方法。 第一次创建[ScrollPosition]时为null。
ScrollPosition createScrollPosition(ScrollPhysics physics,
ScrollContext context, ScrollPosition oldPosition) {
ScrollContext context, ScrollPosition? oldPosition) {
return ShopScrollPosition(
coordinator: coordinator,
physics: physics,
+16 -16
View File
@@ -12,15 +12,15 @@ class ShopScrollCoordinator {
/// 页面主 CustomScrollView 控制
final String pageLabel = "page";
ShopScrollController _pageScrollController;
double Function() pinnedHeaderSliverHeightBuilder;
ShopScrollController? _pageScrollController;
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]) {
@@ -28,27 +28,27 @@ class ShopScrollCoordinator {
_pageInitialOffset = initialOffset;
_pageScrollController = ShopScrollController(this,
debugLabel: pageLabel, initialScrollOffset: initialOffset);
return _pageScrollController;
return _pageScrollController!;
}
/// 创建并获取一个子滑动控制器
ShopScrollController newChildScrollController([String debugLabel]) =>
ShopScrollController(this, debugLabel: debugLabel);
ShopScrollController newChildScrollController([String? debugLabel]) =>
ShopScrollController(this, debugLabel: debugLabel ?? '');
/// 子部件滑动数据协调
/// [userScrollDirection]用户滑动方向
/// [position]被滑动的子部件的位置信息
void applyUserOffset(double delta,
[ScrollDirection userScrollDirection, ShopScrollPosition position]) {
[ScrollDirection? userScrollDirection, ShopScrollPosition? position]) {
if (userScrollDirection == ScrollDirection.reverse) {
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
updateUserScrollDirection(_pageScrollPosition, userScrollDirection!);
final innerDelta = _pageScrollPosition.applyClampedDragUpdate(delta);
if (innerDelta != 0.0) {
updateUserScrollDirection(position, userScrollDirection);
updateUserScrollDirection(position!, userScrollDirection);
position.applyFullDragUpdate(innerDelta);
}
} else {
updateUserScrollDirection(position, userScrollDirection);
updateUserScrollDirection(position!, userScrollDirection!);
final outerDelta = position.applyClampedDragUpdate(delta);
if (outerDelta != 0.0) {
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
@@ -60,7 +60,7 @@ class ShopScrollCoordinator {
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent,
ShopScrollPosition position) {
if (pinnedHeaderSliverHeightBuilder != null) {
maxScrollExtent = maxScrollExtent - pinnedHeaderSliverHeightBuilder();
maxScrollExtent = maxScrollExtent - pinnedHeaderSliverHeightBuilder!();
maxScrollExtent = math.max(0.0, maxScrollExtent);
}
return position.applyContentDimensions(
@@ -77,9 +77,9 @@ class ShopScrollCoordinator {
/// 当手指离开屏幕
void onPointerUp(PointerUpEvent event) {
final double _pagePixels = _pageScrollPosition.pixels;
if (0.0 < _pagePixels && _pagePixels < _pageInitialOffset) {
if (0.0 < _pagePixels && _pagePixels < _pageInitialOffset!) {
if (pageExpand == PageExpandState.NotExpand &&
_pageInitialOffset - _pagePixels > _scrollRedundancy) {
_pageInitialOffset! - _pagePixels > _scrollRedundancy) {
_pageScrollPosition
.animateTo(0.0,
duration: const Duration(milliseconds: 400), curve: Curves.ease)
@@ -87,7 +87,7 @@ class ShopScrollCoordinator {
} else {
pageExpand = PageExpandState.Expanding;
_pageScrollPosition
.animateTo(_pageInitialOffset,
.animateTo(_pageInitialOffset!,
duration: const Duration(milliseconds: 400), curve: Curves.ease)
.then((value) => pageExpand = PageExpandState.NotExpand);
}
+16 -16
View File
@@ -11,17 +11,17 @@ import 'shop_scroll_coordinator.dart';
class ShopScrollPosition extends ScrollPosition
implements ScrollActivityDelegate {
final ShopScrollCoordinator coordinator; // 协调器
ScrollDragController _currentDrag;
ScrollDragController? _currentDrag;
double _heldPreviousVelocity = 0.0;
ShopScrollPosition(
{@required ScrollPhysics physics,
@required ScrollContext context,
{required ScrollPhysics physics,
required ScrollContext context,
double initialPixels = 0.0,
bool keepScrollOffset = true,
ScrollPosition oldPosition,
String debugLabel,
@required this.coordinator})
ScrollPosition? oldPosition,
String? debugLabel,
required this.coordinator})
: super(
physics: physics,
context: context,
@@ -40,7 +40,7 @@ class ShopScrollPosition extends ScrollPosition
@override
double setPixels(double newPixels) {
assert(activity.isScrolling);
assert(activity!.isScrolling);
return super.setPixels(newPixels);
}
@@ -51,13 +51,13 @@ class ShopScrollPosition extends ScrollPosition
goIdle();
return;
}
activity.updateDelegate(this);
activity!.updateDelegate(this);
final ShopScrollPosition typedOther = other as ShopScrollPosition;
_userScrollDirection = typedOther._userScrollDirection;
assert(_currentDrag == null);
if (typedOther._currentDrag != null) {
_currentDrag = typedOther._currentDrag;
_currentDrag.updateDelegate(this);
_currentDrag?.updateDelegate(this);
typedOther._currentDrag = null;
}
}
@@ -131,14 +131,14 @@ class ShopScrollPosition extends ScrollPosition
}
@override
void beginActivity(ScrollActivity newActivity) {
void beginActivity(ScrollActivity? newActivity) {
_heldPreviousVelocity = 0.0;
if (newActivity == null) return;
assert(newActivity.delegate == this);
super.beginActivity(newActivity);
_currentDrag?.dispose();
_currentDrag = null;
if (!activity.isScrolling) updateUserScrollDirection(ScrollDirection.idle);
if (!activity!.isScrolling) updateUserScrollDirection(ScrollDirection.idle);
}
/// 将[用户滚动方向]设置为给定值。
@@ -154,7 +154,7 @@ class ShopScrollPosition extends ScrollPosition
@override
ScrollHoldController hold(VoidCallback holdCancelCallback) {
final double previousVelocity = activity.velocity;
final double previousVelocity = activity!.velocity;
final HoldScrollActivity holdActivity =
HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback);
beginActivity(holdActivity);
@@ -195,10 +195,10 @@ class ShopScrollPosition extends ScrollPosition
if (coordinator.pageExpand == PageExpandState.Expanding) return;
}
assert(pixels != null);
final Simulation simulation =
final Simulation? simulation =
physics.createBallisticSimulation(this, velocity);
if (simulation != null) {
beginActivity(BallisticScrollActivity(this, simulation, context.vsync));
beginActivity(BallisticScrollActivity(this, simulation, context.vsync, true));
} else {
goIdle();
}
@@ -220,8 +220,8 @@ class ShopScrollPosition extends ScrollPosition
@override
Future<void> animateTo(
double to, {
@required Duration duration,
@required Curve curve,
required Duration duration,
required Curve curve,
}) {
if (nearEqual(to, pixels, physics.tolerance.distance)) {
// 跳过动画,直接移到我们已经靠近的位置。
+22 -183
View File
@@ -6,15 +6,11 @@ import 'dart:ui' as ui;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
// import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:stripe_payment/stripe_payment.dart';
import '../constants.dart';
@@ -42,174 +38,17 @@ class Util {
}
Util._internal();
// final FirebaseMessaging firebaseMessaging = FirebaseMessaging();
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
static bool notificationTapped = false;
init() {
// initLocalNotification();
// initFirebaseMessaging();
// 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:')) {
static Widget showImage(String imageUrl, {double? width, double? height,
BoxFit? fit, Widget Function(BuildContext, String, dynamic)? errorWidget}) {
if (imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
return CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: width,
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) {
return Image.asset(
'assets/images/not_found.png',
@@ -219,13 +58,13 @@ class Util {
);
},
);
} else if (imageUrl != null && imageUrl.isNotEmpty) {
} else if (imageUrl.isNotEmpty) {
return Image.file(
File(imageUrl),
width: width,
height: height,
fit: fit,
errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) {
errorBuilder: (BuildContext context, Object object, StackTrace? stackTrace) {
return Image.asset(
'assets/images/not_found.png',
width: width,
@@ -319,14 +158,14 @@ class Util {
final picker = ImagePicker();
var image = await picker.pickImage(source: ImageSource.gallery);
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 {
final picker = ImagePicker();
var image = await picker.pickImage(source: ImageSource.camera);
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 {
@@ -456,18 +295,18 @@ class Util {
ImagePicker picker = ImagePicker();
var image = await picker.pickImage(source: ImageSource.gallery);
Navigator.of(context).pop();
onGotFile(imageId, image.path);
onGotFile(imageId, image!.path);
}
void getPictureFromCamera2(BuildContext context, int imageId, OnGotFile onGotFile) async {
ImagePicker picker = ImagePicker();
var image = await picker.pickImage(source: ImageSource.camera);
Navigator.of(context).pop();
onGotFile(imageId, image.path);
onGotFile(imageId, image!.path);
}
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();
formData.fields.add(MapEntry("msg", msg));
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
@@ -562,7 +401,7 @@ class Util {
PaymentPlatform paymentPlatform, {
bool googlePay=false,
bool applePay=false,
StripePaymentMethod stripePaymentMethod,
StripePaymentMethod? stripePaymentMethod,
}) {
switch(paymentPlatform.code) {
case Constants.PAYMENT_METHOD_CODE_SQUARE:
@@ -587,7 +426,7 @@ class Util {
StripePayment.setOptions(
StripeOptions(publishableKey: paymentPlatform.publishableKey,
merchantId: paymentPlatform.merchantId,
androidPayMode: paymentPlatform.publishableKey.contains('_test_')
androidPayMode: paymentPlatform.publishableKey!.contains('_test_')
? 'test'
: 'production'
)
@@ -595,14 +434,14 @@ class Util {
StripePayment.paymentRequestWithNativePay(
androidPayOptions: AndroidPayPaymentRequest(
totalPrice: order.totalPrice.toStringAsFixed(2),
currencyCode: order.businessInfo.currency,
currencyCode: order.businessInfo!.currency,
),
applePayOptions: ApplePayPaymentOptions(
currencyCode: order.businessInfo.currency,
countryCode: order.businessInfo.address.country,
currencyCode: order.businessInfo!.currency,
countryCode: order.businessInfo!.address.country,
items: [
ApplePayItem(
label: order.businessInfo.name,
label: order.businessInfo!.name,
amount: order.totalPrice.toStringAsFixed(2),
),
],
@@ -633,7 +472,7 @@ class Util {
),
);
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) {
StripePayment.confirmPaymentIntent(
PaymentIntent(
@@ -643,8 +482,8 @@ class Util {
).then((paymentIntentResult) {
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
Utils.stripeChargedSuccess(order,
paymentMethod.id,
paymentIntentResult.paymentIntentId,
paymentMethod.id!,
paymentIntentResult.paymentIntentId!,
(response) {
StripePayment.completeNativePayRequest().then((_) {
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);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
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
View File
@@ -5,8 +5,6 @@ import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/material.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 '../constants.dart';
@@ -36,27 +34,21 @@ class Util {
}
static Future<Box> getBox() async {
Hive.initFlutter();
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}) {
static Widget showImage(String imageUrl, {double? width, double? height,
BoxFit? fit, Widget Function(BuildContext, String?, dynamic)? errorWidget}) {
return Image.network(imageUrl,
fit: fit,
width: width,
height: height,
cacheWidth: width != null ? width.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;
return Center(
child: Utils.imageLoadingIndicator(),
);
},
errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) {
errorBuilder: (BuildContext context, Object object, StackTrace? stackTrace) {
print('StackTrace: $stackTrace');
if (errorWidget != 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 {
InputElement uploadInput = FileUploadInputElement();
InputElement uploadInput = FileUploadInputElement() as InputElement;
uploadInput.click();
uploadInput.onChange.listen((e) {
final files = uploadInput.files;
if (files.length >= 1) {
if (files!.length >= 1) {
final File file = files[0];
final FileReader reader = new FileReader();
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) {
Fluttertoast.showToast(
@@ -210,11 +202,11 @@ class Util {
void startFilePicker2(BuildContext context, int imageId, OnGotFile onGotFile) async {
final _image = await WebImagePicker().pickImage();
onGotFile(imageId, _image.dataScheme);
onGotFile(imageId, _image!.dataScheme);
}
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();
formData.fields.add(MapEntry("msg", msg));
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
@@ -252,7 +244,7 @@ class Util {
PaymentPlatform paymentPlatform, {
bool googlePay=false,
bool applePay=false,
StripePaymentMethod stripePaymentMethod,
StripePaymentMethod? stripePaymentMethod,
}) {
switch(paymentPlatform.code) {
case Constants.PAYMENT_METHOD_CODE_SQUARE:
@@ -293,36 +285,36 @@ class Util {
}
class ImageInfo {
String name;
String data;
String dataScheme;
String path;
String name = '';
String data = '';
String dataScheme = '';
String path = '';
}
class WebImagePicker {
Future<ImageInfo> pickImage() async {
Future<ImageInfo?> pickImage() async {
print('pickImage');
final ImageInfo data = ImageInfo();
final FileUploadInputElement input = FileUploadInputElement();
document.body.children.add(input);
document.body!.children.add(input);
input..accept = 'image/*';
input.click();
await input.onChange.first;
if (input.files.isEmpty) return null;
if (input.files!.isEmpty) return null;
final reader = FileReader();
reader.readAsDataUrl(input.files[0]);
reader.readAsDataUrl(input.files![0]);
await reader.onLoad.first;
final encoded = reader.result as String;
// remove data:image/*;base64 preambule
final stripped =
encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), '');
//final imageBase64 = base64.decode(stripped);
final imageName = input.files?.first?.name;
final imagePath = input.files?.first?.relativePath;
data.name = imageName;
final imageName = input.files?.first.name;
final imagePath = input.files?.first.relativePath;
data.name = imageName!;
data.data = stripped;
data.dataScheme = encoded;
data.path = imagePath;
data.path = imagePath!;
return data;
}
}
+145 -160
View File
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_wisetronic/models/product.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:path/path.dart' as path;
import 'package:universal_io/io.dart';
@@ -28,6 +27,7 @@ import '../store/actions.dart';
import '../store/store.dart';
import 'http_util.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 OnError(dynamic error);
@@ -37,16 +37,25 @@ typedef void OnOk();
typedef void OnLoadImageError();
class Utils {
static StreamSubscription onProductWillAddToCartSubscription;
static StreamSubscription onProductWillRemoveFromCartSubscription;
static final Utils _utils = Utils._internal();
static SharedPreferences? prefs;
static StreamSubscription? onProductWillAddToCartSubscription;
static StreamSubscription? onProductWillRemoveFromCartSubscription;
factory Utils() {
return _utils;
}
Utils._internal();
init() async {
prefs = await SharedPreferences.getInstance();
onProductWillAddToCartSubscription =
eventBus.on<OnProductWillAddToCart>().listen((event) {
CartInfo cartInfo =
CartInfo? cartInfo =
getCartInfoByBusiness(store.state.cartInfos, event.business);
if (cartInfo == null || cartInfo.productList.length == 0) {
if (cartInfo == null || cartInfo.productList!.length == 0) {
cartInfo = CartInfo();
cartInfo.id = 0;
cartInfo.amountPaid = 0.0;
@@ -57,7 +66,7 @@ class Utils {
id: 0,
price: event.price,
product: event.product,
name: event.product.name,
name: event.product.name!,
description: event.description,
quantity: 1.0);
cartInfo.productList = [lineItem];
@@ -67,15 +76,15 @@ class Utils {
eventBus.fire(new OnCartInfoUpdated());
print('#1');
} else {
if (event.product.productAttributes.length > 0) {
if (event.product.productAttributes!.length > 0) {
CartLineItem lineItem = newCartLineItem(
id: 0,
price: event.price,
product: event.product,
name: event.product.name,
name: event.product.name!,
description: event.description,
quantity: 1.0);
cartInfo.productList.add(lineItem);
cartInfo.productList!.add(lineItem);
addSubproductToCard(cartInfo, lineItem);
store.dispatch(new UpdateCartInfo(addCartInfoToCartInfoList(
store.state.cartInfos, cartInfo)));
@@ -83,8 +92,8 @@ class Utils {
eventBus.fire(new OnCartInfoUpdated());
} else {
int found = -1;
for (var i = 0; i < cartInfo.productList.length; i++) {
if (event.product.id == cartInfo.productList[i].product.id) {
for (var i = 0; i < cartInfo.productList!.length; i++) {
if (event.product.id == cartInfo.productList![i].product!.id) {
found = i;
break;
}
@@ -94,18 +103,18 @@ class Utils {
id: 0,
price: event.price,
product: event.product,
name: event.product.name,
name: event.product.name!,
description: event.description,
quantity: 1.0);
cartInfo.productList.add(lineItem);
cartInfo.productList!.add(lineItem);
addSubproductToCard(cartInfo, lineItem);
store.dispatch(new UpdateCartInfo(addCartInfoToCartInfoList(
store.state.cartInfos, cartInfo)));
print('#3');
eventBus.fire(new OnCartInfoUpdated());
} else {
if (cartInfo.productList[found].quantity + 1.0 >
event.product.leftNum) {
if (cartInfo.productList![found].quantity! + 1.0 >
event.product.leftNum!) {
// Fluttertoast.showToast(
// msg: S.of(context).product_insufficient,
// toastLength: Toast.LENGTH_SHORT,
@@ -113,8 +122,8 @@ class Utils {
// backgroundColor: Colors.red,
// textColor: Colors.white);
} else {
cartInfo.productList[found].quantity += 1;
addSubproductQty(cartInfo, cartInfo.productList[found]);
cartInfo.productList![found].quantity = cartInfo.productList![found].quantity! + 1;
addSubproductQty(cartInfo, cartInfo.productList![found]);
store.dispatch(new UpdateCartInfo(
addCartInfoToCartInfoList(
store.state.cartInfos, cartInfo)));
@@ -127,41 +136,41 @@ class Utils {
});
onProductWillRemoveFromCartSubscription =
eventBus.on<OnProductWillRemoveFromCart>().listen((event) {
CartInfo cartInfo =
CartInfo? cartInfo =
getCartInfoByBusiness(store.state.cartInfos, event.business);
if (cartInfo != null) {
if (cartInfo.productList.length > 0) {
if (cartInfo.productList!.length > 0) {
if (event.productListIndex != -1) {
if (cartInfo.productList[event.productListIndex].quantity <= 1) {
String uuid = cartInfo.productList[event.productListIndex].uuid;
cartInfo.productList.removeAt(event.productListIndex);
if (cartInfo.productList![event.productListIndex].quantity! <= 1) {
String uuid = cartInfo.productList![event.productListIndex].uuid!;
cartInfo.productList!.removeAt(event.productListIndex);
removeSubproduct(cartInfo, uuid);
} else {
cartInfo.productList[event.productListIndex].quantity -= 1;
addSubproductQty(cartInfo, cartInfo.productList[event.productListIndex], remove: true);
cartInfo.productList![event.productListIndex].quantity = cartInfo.productList![event.productListIndex].quantity! - 1;
addSubproductQty(cartInfo, cartInfo.productList![event.productListIndex], remove: true);
}
} else {
int productListIndex = -1;
for (var i = 0; i < cartInfo.productList.length; i++) {
if (cartInfo.productList[i].product.id == event.product.id) {
for (var i = 0; i < cartInfo.productList!.length; i++) {
if (cartInfo.productList![i].product!.id == event.product.id) {
productListIndex = i;
break;
}
}
if (productListIndex != -1) {
if (cartInfo.productList[productListIndex].quantity <= 1) {
String uuid = cartInfo.productList[productListIndex].uuid;
cartInfo.productList.removeAt(productListIndex);
if (cartInfo.productList![productListIndex].quantity! <= 1) {
String uuid = cartInfo.productList![productListIndex].uuid!;
cartInfo.productList!.removeAt(productListIndex);
removeSubproduct(cartInfo, uuid);
} else {
cartInfo.productList[productListIndex].quantity -= 1;
addSubproductQty(cartInfo, cartInfo.productList[productListIndex], remove: true);
cartInfo.productList![productListIndex].quantity = cartInfo.productList![productListIndex].quantity! - 1;
addSubproductQty(cartInfo, cartInfo.productList![productListIndex], remove: true);
}
}
}
}
if (cartInfo.productList.length <= 0) {
if (cartInfo.productList!.length <= 0) {
store.dispatch(UpdateCartInfo(removeCartInfoFromCartInfoList(
store.state.cartInfos, cartInfo)));
} else {
@@ -211,7 +220,7 @@ class Utils {
return false;
}
static Map<String, dynamic> stringToJson(String string) {
static Map<String, dynamic>? stringToJson(String? string) {
if (string == null || string.isEmpty) {
return null;
}
@@ -244,10 +253,6 @@ class Utils {
return platformName;
}
static Future<Box> getBox() async {
return Util.getBox();
}
static Widget imageLoadingIndicator(
{double width = 30.0, double height = 30.0}) {
return SizedBox(
@@ -365,7 +370,7 @@ class Utils {
);
}
static showLoadingDialog(BuildContext context, {String message}) {
static showLoadingDialog(BuildContext context, {String? message}) {
showDialog(
context: context,
barrierDismissible: false,
@@ -468,46 +473,45 @@ class Utils {
}
static void getCurrentUser() {
getBox().then((box) {
int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0);
String accessToken =
box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
if (userId == 0 || accessToken.isEmpty) {
eventBus.fire(new OnGetCurrentUserFailed(Error()));
} else {
HttpUtil.httpGet(
'v1/users/$userId',
queryParameters: {},
businessId: Constants.BUSINESS_ID,
).then((value) {
User user = User.fromJson(value);
store.dispatch(UpdateCurrentUser(user));
eventBus.fire(OnCurrentUserUpdated());
}).catchError((err) {
eventBus.fire(new OnGetCurrentUserFailed(err));
});
}
}).catchError((err) {
eventBus.fire(new OnGetCurrentUserFailed(err));
});
if (store.state.user != null) {
eventBus.fire(OnCurrentUserUpdated());
return;
}
int? userId = prefs?.getInt(Constants.KEY_USER_ID) ?? 0;
String? accessToken = prefs?.getString(Constants.KEY_ACCESS_TOKEN) ?? '';
if (userId == 0 || accessToken.isEmpty) {
eventBus.fire(new OnGetCurrentUserFailed(Error()));
} else {
HttpUtil.httpGet(
'v1/users/$userId',
queryParameters: {},
businessId: Constants.BUSINESS_ID,
).then((value) {
User user = User.fromJson(value);
store.dispatch(UpdateCurrentUser(user));
eventBus.fire(OnCurrentUserUpdated());
}).catchError((err) {
eventBus.fire(new OnGetCurrentUserFailed(err));
});
}
}
static void showMessageDialog(BuildContext context, dynamic error,
{String title, List<Widget> actions, OnOk onOk}) {
{String? title, List<Widget>? actions, OnOk? onOk}) {
String message = '';
print('error $error');
if (error is DioError && error.response != null) {
if (error.response.data['message'] != null) {
message = error.response.data['message'];
} else if (error.response.data['error'] != null) {
if (error.response.data['error'] is String) {
message = error.response.data['error'];
} else if (error.response.data['error']['errmsg'] != null) {
message = error.response.data['error']['errmsg'];
} else if (error.response.data['error']['message'] != null) {
message = error.response.data['error']['message'];
if (error is DioException && error.response != null) {
if (error.response!.data['message'] != null) {
message = error.response!.data['message'];
} else if (error.response!.data['error'] != null) {
if (error.response!.data['error'] is String) {
message = error.response!.data['error'];
} else if (error.response!.data['error']['errmsg'] != null) {
message = error.response!.data['error']['errmsg'];
} else if (error.response!.data['error']['message'] != null) {
message = error.response!.data['error']['message'];
} else {
message = error.response.data.toString();
message = error.response!.data.toString();
}
}
} else if (error is String) {
@@ -540,20 +544,15 @@ class Utils {
}
static Future<List<int>> getLastVisit() async {
Box box = await getBox();
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();
store.dispatch(UpdateLastVisit(lv));
return lv;
}
static void saveLastVisit(List<int> lastVisit) {
getBox().then((box) {
box.put(Constants.KEY_LAST_VISIT, lastVisit.toString());
}).catchError((error) {
print('Error occurred: $error');
});
prefs?.setString(Constants.KEY_LAST_VISIT, lastVisit.toString());
}
static String utcDatetimeStringToLocalDatetimeString(
@@ -656,26 +655,26 @@ class Utils {
}
}
static CartInfo getCartInfoByBusiness(
List<CartInfo> cartInfos, Business business) {
static CartInfo? getCartInfoByBusiness(
List<CartInfo>? cartInfos, Business business) {
if (cartInfos == null || cartInfos.length <= 0) {
return null;
}
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 null;
}
static CartInfo getCartInfoByBusinessId(
List<CartInfo> cartInfos, int businessId) {
static CartInfo? getCartInfoByBusinessId(
List<CartInfo>? cartInfos, int businessId) {
if (cartInfos == null || cartInfos.length <= 0) {
return null;
}
for (var i = 0; i < cartInfos.length; i++) {
if (cartInfos[i].businessInfo.id == businessId) {
if (cartInfos[i].businessInfo!.id == businessId) {
return cartInfos[i];
}
}
@@ -683,13 +682,13 @@ class Utils {
}
static List<CartInfo> addCartInfoToCartInfoList(
List<CartInfo> cartInfos, CartInfo cartInfo) {
List<CartInfo>? cartInfos, CartInfo cartInfo) {
if (cartInfos == null || cartInfos.length <= 0) {
cartInfos = [cartInfo];
} else {
bool found = false;
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;
found = true;
break;
@@ -699,22 +698,19 @@ class Utils {
cartInfos.add(cartInfo);
}
}
Utils.getBox().then((box) {
box.put(Constants.KEY_CARTINFOS, cartInfos.toString());
}).catchError((error) {
print('Error occurred: $error');
});
prefs?.setString(Constants.KEY_CARTINFOS, cartInfos.toString());
return cartInfos;
}
static List<CartInfo> removeCartInfoFromCartInfoList(
List<CartInfo> cartInfos, CartInfo cartInfo) {
List<CartInfo>? cartInfos, CartInfo? cartInfo) {
if (cartInfos == null || cartInfos.length <= 0 || cartInfo == null) {
prefs?.setString(Constants.KEY_CARTINFOS, cartInfos.toString());
return [];
}
int removeIndex = -1;
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;
break;
}
@@ -722,17 +718,13 @@ class Utils {
if (removeIndex != -1) {
cartInfos.removeAt(removeIndex);
}
getBox().then((box) {
box.put(Constants.KEY_CARTINFOS, cartInfos.toString());
}).catchError((error) {
print('Error occurred: $error');
});
prefs?.setString(Constants.KEY_CARTINFOS, cartInfos.toString());
return cartInfos;
}
static void clearCart(BuildContext context, CartInfo cartInfo,
{OnComplete onComplete}) {
if (cartInfo != null && cartInfo.productList.length > 0) {
{OnComplete? onComplete}) {
if (cartInfo != null && cartInfo.productList!.length > 0) {
showDialog(
context: context,
builder: (BuildContext context) {
@@ -766,7 +758,7 @@ class Utils {
}
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), () {
if (returnUrl != null) {
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+');
return intInStr.firstMatch(numString).group(0);
return intInStr.firstMatch(numString)?.group(0);
}
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);
switch(date.weekday) {
case 1:
@@ -854,61 +846,54 @@ class Utils {
} else {
return S.of(context).monday;
}
break;
case 2:
if (shortName) {
return S.of(context).tue;
} else {
return S.of(context).tuesday;
}
break;
case 3:
if (shortName) {
return S.of(context).wed;
} else {
return S.of(context).wednesday;
}
break;
case 4:
if (shortName) {
return S.of(context).thu;
} else {
return S.of(context).thursday;
}
break;
case 5:
if (shortName) {
return S.of(context).fri;
} else {
return S.of(context).friday;
}
break;
case 6:
if (shortName) {
return S.of(context).sat;
} else {
return S.of(context).saturday;
}
break;
case 7:
if (shortName) {
return S.of(context).sun;
} else {
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,
{
String cardBrand,
String cardCountry,
int cardExpMonth,
int cardExpYear,
String cardFunding,
String cardLast4,
String? cardBrand,
String? cardCountry,
int? cardExpMonth,
int? cardExpYear,
String? cardFunding,
String? cardLast4,
}) {
HttpUtil.httpPost('v1/stripe-payment-intent',
@@ -919,12 +904,12 @@ class Utils {
'customer_id': customerId == null ? '' : customerId,
'payment_method_id': paymentMethodId,
'payment_method_type': cardType,
'card_brand': cardBrand,
'card_country': cardCountry,
'card_exp_month': cardExpMonth,
'card_exp_year': cardExpYear,
'card_funding': cardFunding,
'card_last4': cardLast4,
'card_brand': cardBrand ?? '',
'card_country': cardCountry ?? '',
'card_exp_month': cardExpMonth ?? 0,
'card_exp_year': cardExpYear ?? 0,
'card_funding': cardFunding ?? '',
'card_last4': cardLast4 ?? '',
},
isFormData: true,
).catchError(onError);
@@ -945,8 +930,8 @@ class Utils {
static int getProductLineInOrder(CartInfo cartInfo) {
int qty = 0;
for (CartLineItem lineItem in cartInfo.productList) {
if (lineItem.product.id != null) {
for (CartLineItem lineItem in cartInfo.productList!) {
if (lineItem.product?.id != null) {
qty += 1;
}
}
@@ -954,10 +939,10 @@ class Utils {
}
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)));
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) {
@@ -978,42 +963,42 @@ class Utils {
}
static CartLineItem newCartLineItem(
{int id,
double price,
Product product,
String name,
String description,
double quantity,
String parentUuid,
{int? id,
double? price,
Product? product,
String? name,
String? description,
double? quantity,
String? parentUuid,
}) {
CartLineItem lineItem = CartLineItem();
lineItem.unitPrice = price;
lineItem.product = product;
lineItem.name = product.name;
lineItem.name = product?.name ?? '';
lineItem.description = description;
lineItem.quantity = quantity;
lineItem.parentUuid = parentUuid;
lineItem.parentUuid = parentUuid ?? '';
return lineItem;
}
static void addSubproductToCard(CartInfo cartInfo, CartLineItem lineItem) {
if (lineItem.product.subproducts.length > 0) {
for (int j = 0; j < lineItem.product.subproducts.length; j++) {
cartInfo.productList.add(
if (lineItem.product!.subproducts!.length > 0) {
for (int j = 0; j < lineItem.product!.subproducts!.length; j++) {
cartInfo.productList!.add(
newCartLineItem(
id: 0,
price: 0.0,
product: Product(
lineItem.product.subproducts[j].product.id,
lineItem.product.businessId,
lineItem.product.subproducts[j].product.name,
lineItem.product.subproducts[j].product.price,
lineItem.product.subproducts[j].product.description,
lineItem.product.subproducts[j].product.image,
lineItem.product.subproducts[j].product.productAttributes
lineItem.product!.subproducts![j].product.id,
lineItem.product!.businessId!,
lineItem.product!.subproducts![j].product.name,
lineItem.product!.subproducts![j].product.price,
lineItem.product!.subproducts![j].product.description,
lineItem.product!.subproducts![j].product.image,
lineItem.product!.subproducts![j].product.productAttributes
),
name: lineItem.product.subproducts[j].product.name,
description: lineItem.product.subproducts[j].product.description,
name: lineItem.product!.subproducts![j].product.name,
description: lineItem.product!.subproducts![j].product.description,
quantity: 1.0,
parentUuid: lineItem.uuid
)
@@ -1024,16 +1009,16 @@ class Utils {
static void addSubproductQty(CartInfo cartInfo, CartLineItem lineItem,
{bool remove = false}) {
if (lineItem.product.subproducts.length > 0) {
for (var i = 0; i < cartInfo.productList.length; i++) {
CartLineItem lineItem1 = cartInfo.productList[i];
if (lineItem.product!.subproducts!.length > 0) {
for (var i = 0; i < cartInfo.productList!.length; i++) {
CartLineItem lineItem1 = cartInfo.productList![i];
if (lineItem1.parentUuid == lineItem.uuid) {
for (var j = 0; j < lineItem.product.subproducts.length; j++) {
if (lineItem.product.subproducts[j].product.id == lineItem1.product.id) {
for (var j = 0; j < lineItem.product!.subproducts!.length; j++) {
if (lineItem.product!.subproducts![j].product.id == lineItem1.product!.id) {
if (remove) {
lineItem1.quantity -= lineItem.product.subproducts[j].quantity;
lineItem1.quantity = lineItem1.quantity! - lineItem.product!.subproducts![j].quantity;
} else {
lineItem1.quantity += lineItem.product.subproducts[j].quantity;
lineItem1.quantity = lineItem1.quantity! + lineItem.product!.subproducts![j].quantity;
}
break;
}
@@ -1044,7 +1029,7 @@ class Utils {
}
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 {
@@ -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(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -1119,7 +1104,7 @@ class Utils {
}
class RuntimeError extends Error {
final int code;
final int? code;
final String message;
RuntimeError(this.message, {this.code});
String toString() => "Runtime Error: $message";
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+12
View File
@@ -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))});
}
+20
View File
@@ -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 }}
+140
View File
@@ -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
+74
View File
@@ -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
+10
View File
@@ -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
+290
View File
@@ -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
+26
View File
@@ -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.
+247
View File
@@ -0,0 +1,247 @@
[![pub package](https://img.shields.io/pub/v/stripe_sdk.svg)](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
View File
@@ -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
View File
@@ -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;
}
}
+119
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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];
}
+185
View File
@@ -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