diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..acd575f6
--- /dev/null
+++ b/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/app/src/main/kotlin/co/paystack/example/MainActivity.kt b/example/android/app/src/main/kotlin/co/paystack/example/MainActivity.kt
new file mode 100644
index 00000000..004d6c03
--- /dev/null
+++ b/example/android/app/src/main/kotlin/co/paystack/example/MainActivity.kt
@@ -0,0 +1,6 @@
+package co.paystack.example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 00000000..f74085f3
--- /dev/null
+++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 00000000..449a9f93
--- /dev/null
+++ b/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 00000000..acd575f6
--- /dev/null
+++ b/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
new file mode 100644
index 00000000..70693e4a
--- /dev/null
+++ b/example/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 00000000..308a2a56
--- /dev/null
+++ b/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 14e5eeb6..cb2ba4a7 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_paystack/flutter_paystack.dart';
import 'package:http/http.dart' as http;
+import 'package:flutter/foundation.dart';
// To get started quickly, change this to your heroku deployment of
// https://github.com/PaystackHQ/sample-charge-card-backend
@@ -15,10 +16,15 @@ import 'package:http/http.dart' as http;
// Step 5. Replace {YOUR_BACKEND_URL} below with the url generated by heroku (format https://some-url.herokuapp.com)
String backendUrl = '{YOUR_BACKEND_URL}';
// Set this to a public key that matches the secret key you supplied while creating the heroku instance
-String paystackPublicKey = '{YOUR_PAYSTACK_PUBLIC_KEY}';
+// String paystackPublicKey = '{YOUR_PAYSTACK_PUBLIC_KEY}';
+String paystackPublicKey = '';
const String appName = 'Paystack Example';
-void main() => runApp(new MyApp());
+void main() {
+ if (kIsWeb) WidgetsFlutterBinding.ensureInitialized();
+
+ runApp(new MyApp());
+}
class MyApp extends StatelessWidget {
@override
@@ -53,7 +59,9 @@ class _HomePageState extends State {
String? _cvv;
int? _expiryMonth;
int? _expiryYear;
-
+ String _email = '';
+ int _amount = 0;
+ String _message = '';
@override
void initState() {
plugin.initialize(publicKey: paystackPublicKey);
@@ -62,181 +70,257 @@ class _HomePageState extends State {
@override
Widget build(BuildContext context) {
- return new Scaffold(
- key: _scaffoldKey,
- appBar: new AppBar(title: const Text(appName)),
- body: new Container(
- padding: const EdgeInsets.all(20.0),
- child: new Form(
- key: _formKey,
- child: new SingleChildScrollView(
- child: new ListBody(
- children: [
- new Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- new Expanded(
- child: const Text('Initalize transaction from:'),
+ return kIsWeb
+ ? new Scaffold(
+ appBar: AppBar(
+ title: Text('Flutter Paystack Test'),
+ ),
+ body: Padding(
+ padding: EdgeInsets.symmetric(
+ horizontal: MediaQuery.of(context).size.width / 8,
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ _message,
+ style: TextStyle(
+ fontWeight: FontWeight.w600,
+ color: Colors.deepPurple,
),
- new Expanded(
- child: new Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- new RadioListTile(
- value: 0,
- groupValue: _radioValue,
- onChanged: _handleRadioValueChanged,
- title: const Text('Local'),
- ),
- new RadioListTile(
- value: 1,
- groupValue: _radioValue,
- onChanged: _handleRadioValueChanged,
- title: const Text('Server'),
- ),
- ]),
- )
- ],
- ),
- _border,
- _verticalSizeBox,
- new TextFormField(
- decoration: const InputDecoration(
- border: const UnderlineInputBorder(),
- labelText: 'Card number',
),
- onSaved: (String? value) => _cardNumber = value,
- ),
- _verticalSizeBox,
- new Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- new Expanded(
- child: new TextFormField(
- decoration: const InputDecoration(
- border: const UnderlineInputBorder(),
- labelText: 'CVV',
- ),
- onSaved: (String? value) => _cvv = value,
- ),
+ const SizedBox(height: 10),
+ TextField(
+ decoration: InputDecoration(
+ labelText: 'Email',
),
- _horizontalSizeBox,
- new Expanded(
- child: new TextFormField(
- decoration: const InputDecoration(
- border: const UnderlineInputBorder(),
- labelText: 'Expiry Month',
- ),
- onSaved: (String? value) =>
- _expiryMonth = int.tryParse(value ?? ""),
- ),
+ keyboardType: TextInputType.emailAddress,
+ onChanged: (val) {
+ _email = val;
+ },
+ ),
+ TextField(
+ decoration: InputDecoration(
+ labelText: 'Amount',
),
- _horizontalSizeBox,
- new Expanded(
- child: new TextFormField(
+ keyboardType: TextInputType.number,
+ onChanged: (val) {
+ try {
+ _amount = (double.parse(val) * 100).toInt();
+ } catch (e) {}
+ },
+ ),
+ const SizedBox(height: 20),
+ ElevatedButton(
+ onPressed: () async {
+ setState(() {
+ _message = '';
+ });
+
+ final charge = Charge()
+ ..email = _email
+ ..amount = _amount
+ ..reference =
+ 'ref_${DateTime.now().millisecondsSinceEpoch}';
+ final res =
+ await plugin.checkout(context, charge: charge);
+
+ if (res.status) {
+ _message =
+ 'Charge was successful. Ref: ${res.reference}';
+ } else {
+ _message = 'Failed: ${res.message}';
+ }
+ setState(() {});
+ },
+ child: Text('Checkout'),
+ ),
+ ],
+ ),
+ ),
+ )
+ : new Scaffold(
+ key: _scaffoldKey,
+ appBar: new AppBar(title: const Text(appName)),
+ body: new Container(
+ padding: const EdgeInsets.all(20.0),
+ child: new Form(
+ key: _formKey,
+ child: new SingleChildScrollView(
+ child: new ListBody(
+ children: [
+ new Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ new Expanded(
+ child: const Text('Initalize transaction from:'),
+ ),
+ new Expanded(
+ child: new Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ new RadioListTile(
+ value: 0,
+ groupValue: _radioValue,
+ onChanged: _handleRadioValueChanged,
+ title: const Text('Local'),
+ ),
+ new RadioListTile(
+ value: 1,
+ groupValue: _radioValue,
+ onChanged: _handleRadioValueChanged,
+ title: const Text('Server'),
+ ),
+ ]),
+ )
+ ],
+ ),
+ _border,
+ _verticalSizeBox,
+ new TextFormField(
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
- labelText: 'Expiry Year',
+ labelText: 'Card number',
),
- onSaved: (String? value) =>
- _expiryYear = int.tryParse(value ?? ""),
+ onSaved: (String? value) => _cardNumber = value,
),
- )
- ],
- ),
- _verticalSizeBox,
- Theme(
- data: Theme.of(context).copyWith(
- accentColor: green,
- primaryColorLight: Colors.white,
- primaryColorDark: navyBlue,
- textTheme: Theme.of(context).textTheme.copyWith(
- bodyText2: TextStyle(
- color: lightBlue,
+ _verticalSizeBox,
+ new Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ new Expanded(
+ child: new TextFormField(
+ decoration: const InputDecoration(
+ border: const UnderlineInputBorder(),
+ labelText: 'CVV',
+ ),
+ onSaved: (String? value) => _cvv = value,
+ ),
),
- ),
- ),
- child: Builder(
- builder: (context) {
- return _inProgress
- ? new Container(
- alignment: Alignment.center,
- height: 50.0,
- child: Platform.isIOS
- ? new CupertinoActivityIndicator()
- : new CircularProgressIndicator(),
- )
- : new Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- _getPlatformButton(
- 'Charge Card', () => _startAfreshCharge()),
- _verticalSizeBox,
- _border,
- new SizedBox(
- height: 40.0,
+ _horizontalSizeBox,
+ new Expanded(
+ child: new TextFormField(
+ decoration: const InputDecoration(
+ border: const UnderlineInputBorder(),
+ labelText: 'Expiry Month',
+ ),
+ onSaved: (String? value) =>
+ _expiryMonth = int.tryParse(value ?? ""),
+ ),
+ ),
+ _horizontalSizeBox,
+ new Expanded(
+ child: new TextFormField(
+ decoration: const InputDecoration(
+ border: const UnderlineInputBorder(),
+ labelText: 'Expiry Year',
+ ),
+ onSaved: (String? value) =>
+ _expiryYear = int.tryParse(value ?? ""),
+ ),
+ )
+ ],
+ ),
+ _verticalSizeBox,
+ Theme(
+ data: Theme.of(context).copyWith(
+ accentColor: green,
+ primaryColorLight: Colors.white,
+ primaryColorDark: navyBlue,
+ textTheme: Theme.of(context).textTheme.copyWith(
+ bodyText2: TextStyle(
+ color: lightBlue,
),
- new Row(
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- new Flexible(
- flex: 3,
- child: new DropdownButtonHideUnderline(
- child: new InputDecorator(
- decoration: const InputDecoration(
- border: OutlineInputBorder(),
- isDense: true,
- hintText: 'Checkout method',
+ ),
+ ),
+ child: Builder(
+ builder: (context) {
+ return _inProgress
+ ? new Container(
+ alignment: Alignment.center,
+ height: 50.0,
+ child: Platform.isIOS
+ ? new CupertinoActivityIndicator()
+ : new CircularProgressIndicator(),
+ )
+ : new Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _getPlatformButton('Charge Card',
+ () => _startAfreshCharge()),
+ _verticalSizeBox,
+ _border,
+ new SizedBox(
+ height: 40.0,
+ ),
+ new Row(
+ mainAxisAlignment:
+ MainAxisAlignment.spaceBetween,
+ crossAxisAlignment:
+ CrossAxisAlignment.center,
+ children: [
+ new Flexible(
+ flex: 3,
+ child:
+ new DropdownButtonHideUnderline(
+ child: new InputDecorator(
+ decoration:
+ const InputDecoration(
+ border: OutlineInputBorder(),
+ isDense: true,
+ hintText: 'Checkout method',
+ ),
+ child: new DropdownButton<
+ CheckoutMethod>(
+ value: _method,
+ isDense: true,
+ onChanged:
+ (CheckoutMethod? value) {
+ if (value != null) {
+ setState(() =>
+ _method = value);
+ }
+ },
+ items:
+ banks.map((String value) {
+ return new DropdownMenuItem<
+ CheckoutMethod>(
+ value:
+ _parseStringToMethod(
+ value),
+ child: new Text(value),
+ );
+ }).toList(),
+ ),
+ ),
+ ),
),
- child: new DropdownButton<
- CheckoutMethod>(
- value: _method,
- isDense: true,
- onChanged: (CheckoutMethod? value) {
- if (value != null) {
- setState(() => _method = value);
- }
- },
- items: banks.map((String value) {
- return new DropdownMenuItem<
- CheckoutMethod>(
- value:
- _parseStringToMethod(value),
- child: new Text(value),
- );
- }).toList(),
+ _horizontalSizeBox,
+ new Flexible(
+ flex: 2,
+ child: new Container(
+ width: double.infinity,
+ child: _getPlatformButton(
+ 'Checkout',
+ () => _handleCheckout(context),
+ ),
+ ),
),
- ),
- ),
- ),
- _horizontalSizeBox,
- new Flexible(
- flex: 2,
- child: new Container(
- width: double.infinity,
- child: _getPlatformButton(
- 'Checkout',
- () => _handleCheckout(context),
- ),
- ),
- ),
- ],
- )
- ],
- );
- },
+ ],
+ )
+ ],
+ );
+ },
+ ),
+ )
+ ],
),
- )
- ],
+ ),
+ ),
),
- ),
- ),
- ),
- );
+ );
}
void _handleRadioValueChanged(int? value) {
diff --git a/example/web/favicon.png b/example/web/favicon.png
new file mode 100644
index 00000000..8aaa46ac
Binary files /dev/null and b/example/web/favicon.png differ
diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png
new file mode 100644
index 00000000..b749bfef
Binary files /dev/null and b/example/web/icons/Icon-192.png differ
diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png
new file mode 100644
index 00000000..88cfd48d
Binary files /dev/null and b/example/web/icons/Icon-512.png differ
diff --git a/example/web/index.html b/example/web/index.html
new file mode 100644
index 00000000..66f59294
--- /dev/null
+++ b/example/web/index.html
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ example
+
+
+
+
+
+
+
+
+
diff --git a/example/web/manifest.json b/example/web/manifest.json
new file mode 100644
index 00000000..8c012917
--- /dev/null
+++ b/example/web/manifest.json
@@ -0,0 +1,23 @@
+{
+ "name": "example",
+ "short_name": "example",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#0175C2",
+ "theme_color": "#0175C2",
+ "description": "A new Flutter project.",
+ "orientation": "portrait-primary",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icons/Icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/lib/src/common/paystack.dart b/lib/src/common/paystack.dart
index fcf2df33..e150d77b 100644
--- a/lib/src/common/paystack.dart
+++ b/lib/src/common/paystack.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -13,12 +14,14 @@ import 'package:flutter_paystack/src/models/card.dart';
import 'package:flutter_paystack/src/models/charge.dart';
import 'package:flutter_paystack/src/models/checkout_response.dart';
import 'package:flutter_paystack/src/transaction/card_transaction_manager.dart';
+import 'package:flutter_paystack/src/web/paystack_web.dart';
import 'package:flutter_paystack/src/widgets/checkout/checkout_widget.dart';
class PaystackPlugin {
bool _sdkInitialized = false;
String _publicKey = "";
static late PlatformInfo platformInfo;
+ static var _web = PaystackWeb();
/// Initialize the Paystack object. It should be called as early as possible
/// (preferably in initState() of the Widget.
@@ -42,7 +45,6 @@ class PaystackPlugin {
// Using cascade notation to build the platform specific info
try {
-
platformInfo = await PlatformInfo.fromMethodChannel(Utils.methodChannel);
_sdkInitialized = true;
} on PlatformException {
@@ -128,6 +130,8 @@ class PaystackPlugin {
bool hideEmail = false,
bool hideAmount = false,
}) async {
+ if (kIsWeb) return _web.checkout(charge, _publicKey);
+
return _Paystack(publicKey).checkout(
context,
charge: charge,
diff --git a/lib/src/common/platform_info.dart b/lib/src/common/platform_info.dart
index f260da2a..c234df22 100644
--- a/lib/src/common/platform_info.dart
+++ b/lib/src/common/platform_info.dart
@@ -1,5 +1,6 @@
import 'dart:io';
+import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
/// Holds data that's different on Android and iOS
@@ -13,9 +14,19 @@ class PlatformInfo {
// And there should a better way to fucking do this
final pluginVersion = "1.0.5";
- final platform = Platform.operatingSystem;
+ /// Fixing This
+ // final platform = Platform.operatingSystem;
+ String platform() {
+ if (kIsWeb) {
+ return "Web";
+ } else {
+ return Platform.operatingSystem;
+ }
+ }
+
String userAgent = "${platform}_Paystack_$pluginVersion";
- String deviceId = await channel.invokeMethod('getDeviceId') ?? "";
+ String deviceId =
+ kIsWeb ? platform() : await channel.invokeMethod('getDeviceId') ?? "";
return PlatformInfo._(
userAgent: userAgent,
paystackBuild: pluginVersion,
diff --git a/lib/src/web/js/js_stub.dart b/lib/src/web/js/js_stub.dart
new file mode 100644
index 00000000..1923146d
--- /dev/null
+++ b/lib/src/web/js/js_stub.dart
@@ -0,0 +1 @@
+external F allowInterop(F f);
diff --git a/lib/src/web/js/paystack_js.dart b/lib/src/web/js/paystack_js.dart
new file mode 100644
index 00000000..9b39878d
--- /dev/null
+++ b/lib/src/web/js/paystack_js.dart
@@ -0,0 +1,55 @@
+@JS()
+library paystack_js;
+
+import 'package:flutter/material.dart';
+import "package:js/js.dart";
+
+@JS('PaystackPop')
+class PaystackPop {
+ @JS('setup')
+ external static Handler setup(SetupData data);
+}
+
+@JS()
+@anonymous
+class Handler {
+ external void openIframe();
+}
+
+@JS()
+@anonymous
+class SetupData {
+ external factory SetupData({
+ required String key,
+ required String email,
+ required int amount,
+ required String ref,
+ VoidCallback? onClose,
+ PaystackCallback? callback,
+ });
+
+ external String key;
+ external String email;
+ external int amount;
+ external String ref;
+ external VoidCallback onClose;
+ external PaystackCallback callback;
+}
+
+@JS()
+@anonymous
+class ChargeResponse {
+ external String get message;
+
+ external String get reference;
+
+ external String get response;
+
+ external String get status;
+}
+
+typedef PaystackCallback(ChargeResponse response);
+
+Handler setup(SetupData data) {
+ return PaystackPop.setup(data);
+}
diff --git a/lib/src/web/js/paystack_stub.dart b/lib/src/web/js/paystack_stub.dart
new file mode 100644
index 00000000..4297a03d
--- /dev/null
+++ b/lib/src/web/js/paystack_stub.dart
@@ -0,0 +1,40 @@
+import 'package:flutter/material.dart';
+
+Handler setup(SetupData data) {
+ throw UnimplementedError();
+}
+
+abstract class Handler {
+ void openIframe();
+}
+
+class SetupData {
+ final String? key;
+ final String? email;
+ final int amount;
+ final String? ref;
+
+ final VoidCallback? onClose;
+ final PaystackCallback? callback;
+
+ SetupData({
+ required this.key,
+ required this.email,
+ required this.amount,
+ required this.ref,
+ this.onClose,
+ this.callback,
+ });
+}
+
+abstract class ChargeResponse {
+ String get message;
+
+ String get reference;
+
+ String get response;
+
+ String get status;
+}
+
+typedef PaystackCallback(ChargeResponse response);
diff --git a/lib/src/web/paystack_web.dart b/lib/src/web/paystack_web.dart
new file mode 100644
index 00000000..529b30d1
--- /dev/null
+++ b/lib/src/web/paystack_web.dart
@@ -0,0 +1,42 @@
+import 'dart:async';
+
+import 'package:flutter_paystack/flutter_paystack.dart';
+
+import 'js/js_stub.dart' if (dart.library.js) 'package:js/js.dart';
+import 'js/paystack_stub.dart' if (dart.library.js) 'js/paystack_js.dart';
+
+class PaystackWeb {
+ Future checkout(Charge charge, String key) async {
+ final completer = Completer();
+
+ final handler = setup(
+ SetupData(
+ key: key,
+ email: charge.email!,
+ amount: charge.amount,
+ ref: charge.reference!,
+ onClose: allowInterop(
+ () {
+ completer.complete(CheckoutResponse.defaults());
+ },
+ ),
+ callback: allowInterop((response) {
+ completer.complete(
+ CheckoutResponse(
+ message: response.message,
+ reference: response.reference,
+ status: response.status == 'success',
+ method: CheckoutMethod.card,
+ verify: true,
+ card: charge.card ?? PaymentCard.empty(),
+ ),
+ );
+ }),
+ ),
+ );
+
+ handler.openIframe();
+
+ return completer.future;
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index eb3a9b5a..8d56f5a2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,6 +11,7 @@ dependencies:
intl: ^0.17.0
meta: ^1.3.0
async: ^2.5.0
+ js: ^0.6.3
dev_dependencies:
flutter_test: