diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90e98a77..fedf5495 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.1.3-beta01
+
+- Analytics API support
+
## 3.1.2
- Bug fixes for Coinbase Wallet integration
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 77c14581..d5f66c0f 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -1,5 +1,5 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
-versionName=3.1.2
-versionCode=36
+versionName=3.1.3
+versionCode=38
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 0a76f782..91be99cf 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -8,6 +8,8 @@ PODS:
- CoinbaseWalletSDK/CrossPlatform (1.0.4):
- CoinbaseWalletSDK/Client
- Flutter (1.0.0)
+ - flutter_timezone (0.0.1):
+ - Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
@@ -29,6 +31,7 @@ DEPENDENCIES:
- appcheck (from `.symlinks/plugins/appcheck/ios`)
- coinbase_wallet_sdk (from `.symlinks/plugins/coinbase_wallet_sdk/ios`)
- Flutter (from `Flutter`)
+ - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
@@ -47,6 +50,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/coinbase_wallet_sdk/ios"
Flutter:
:path: Flutter
+ flutter_timezone:
+ :path: ".symlinks/plugins/flutter_timezone/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
@@ -63,6 +68,7 @@ SPEC CHECKSUMS:
coinbase_wallet_sdk: 7ccd4e1a7940deba6ba9bd81beece999a2268c15
CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ flutter_timezone: ffb07bdad3c6276af8dada0f11978d8a1f8a20bb
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index d78fb320..de4ead65 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -470,7 +470,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 38;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22;
ENABLE_BITCODE = NO;
@@ -496,7 +496,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 38;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample.RunnerTests;
@@ -514,7 +514,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 38;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample.RunnerTests;
@@ -530,7 +530,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 38;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.web3modal.flutterExample.RunnerTests;
@@ -655,7 +655,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 38;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22;
ENABLE_BITCODE = NO;
@@ -686,7 +686,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 38;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22;
ENABLE_BITCODE = NO;
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 43fcfe1c..90f82e3d 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 3.1.2
+ 3.1.3
CFBundleSignature
????
CFBundleURLTypes
@@ -36,7 +36,7 @@
CFBundleVersion
- 35
+ 38
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart
index 4d3224ac..72fe9385 100644
--- a/example/lib/home_page.dart
+++ b/example/lib/home_page.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
+import 'package:walletconnect_flutter_dapp/widgets/logger_widget.dart';
import 'package:walletconnect_flutter_dapp/widgets/session_widget.dart';
import 'package:walletconnect_flutter_dapp/utils/dart_defines.dart';
import 'package:walletconnect_flutter_dapp/utils/string_constants.dart';
@@ -9,25 +10,33 @@ import 'package:walletconnect_flutter_dapp/utils/string_constants.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
- required this.swapTheme,
- required this.changeTheme,
+ required this.toggleBrightness,
+ required this.toggleTheme,
});
- final VoidCallback swapTheme;
- final VoidCallback changeTheme;
+ final VoidCallback toggleBrightness;
+ final VoidCallback toggleTheme;
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
+ final overlay = OverlayController(const Duration(milliseconds: 200));
late W3MService _w3mService;
@override
void initState() {
super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _toggleOverlay();
+ });
_initializeService();
}
+ void _toggleOverlay() {
+ overlay.show(context);
+ }
+
void _initializeService() async {
// See https://docs.walletconnect.com/web3modal/flutter/custom-chains
W3MChainPresets.chains.putIfAbsent(_celo.chainId, () => _celo);
@@ -36,6 +45,7 @@ class _MyHomePageState extends State {
_w3mService = W3MService(
projectId: DartDefines.projectId,
logLevel: LogLevel.error,
+ enableAnalytics: true,
metadata: const PairingMetadata(
name: StringConstants.w3mPageTitleV3,
description: StringConstants.w3mPageTitleV3,
@@ -74,15 +84,10 @@ class _MyHomePageState extends State {
_w3mService.onModalDisconnect.subscribe(_onModalDisconnect);
_w3mService.onModalError.subscribe(_onModalError);
//
- // _w3mService.onSessionConnectEvent.subscribe(_onSessionConnect);
- // _w3mService.onSessionDeleteEvent.subscribe(_onSessionDelete);
_w3mService.onSessionExpireEvent.subscribe(_onSessionExpired);
_w3mService.onSessionUpdateEvent.subscribe(_onSessionUpdate);
_w3mService.onSessionEventEvent.subscribe(_onSessionEvent);
//
- // _w3mService.onCoinbaseConnect.subscribe((args) {});
- // _w3mService.onCoinbaseError.subscribe((args) {});
- // _w3mService.onCoinbaseSessionUpdate.subscribe((args) {});
//
await _w3mService.init();
}
@@ -95,15 +100,10 @@ class _MyHomePageState extends State {
_w3mService.onModalDisconnect.unsubscribe(_onModalDisconnect);
_w3mService.onModalError.unsubscribe(_onModalError);
//
- // _w3mService.onSessionConnectEvent.unsubscribe(_onSessionConnect);
- // _w3mService.onSessionDeleteEvent.unsubscribe(_onSessionDelete);
_w3mService.onSessionExpireEvent.unsubscribe(_onSessionExpired);
_w3mService.onSessionUpdateEvent.unsubscribe(_onSessionUpdate);
_w3mService.onSessionEventEvent.unsubscribe(_onSessionEvent);
//
- // _w3mService.onCoinbaseConnect.unsubscribe((args) {});
- // _w3mService.onCoinbaseError.unsubscribe((args) {});
- // _w3mService.onCoinbaseSessionUpdate.unsubscribe((args) {});
super.dispose();
}
@@ -154,17 +154,21 @@ class _MyHomePageState extends State {
backgroundColor: Web3ModalTheme.colorsOf(context).background175,
foregroundColor: Web3ModalTheme.colorsOf(context).foreground100,
actions: [
+ IconButton(
+ icon: const Icon(Icons.logo_dev_rounded),
+ onPressed: _toggleOverlay,
+ ),
IconButton(
icon: isCustom
? const Icon(Icons.yard)
: const Icon(Icons.yard_outlined),
- onPressed: widget.changeTheme,
+ onPressed: widget.toggleTheme,
),
IconButton(
icon: Web3ModalTheme.maybeOf(context)?.isDarkMode ?? false
? const Icon(Icons.light_mode_outlined)
: const Icon(Icons.dark_mode_outlined),
- onPressed: widget.swapTheme,
+ onPressed: widget.toggleBrightness,
),
],
),
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 1a40259f..9aa4c72a 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -59,19 +59,19 @@ class _MyAppState extends State with WidgetsBindingObserver {
debugShowCheckedModeBanner: false,
title: StringConstants.w3mPageTitleV3,
home: MyHomePage(
- swapTheme: () => _swapTheme(),
- changeTheme: () => _changeTheme(),
+ toggleTheme: () => _toggleTheme(),
+ toggleBrightness: () => _toggleBrightness(),
),
),
);
}
- void _swapTheme() => setState(() {
- _isDarkMode = !_isDarkMode;
+ void _toggleTheme() => setState(() {
+ _themeData = (_themeData == null) ? _customTheme : null;
});
- void _changeTheme() => setState(() {
- _themeData = (_themeData == null) ? _customTheme : null;
+ void _toggleBrightness() => setState(() {
+ _isDarkMode = !_isDarkMode;
});
Web3ModalThemeData get _customTheme => Web3ModalThemeData(
diff --git a/example/lib/widgets/logger_widget.dart b/example/lib/widgets/logger_widget.dart
new file mode 100644
index 00000000..a6930e31
--- /dev/null
+++ b/example/lib/widgets/logger_widget.dart
@@ -0,0 +1,220 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:web3modal_flutter/services/analytics_service/analytics_service_singleton.dart';
+
+class DraggableCard extends StatefulWidget {
+ final OverlayController overlayController;
+ const DraggableCard({
+ super.key,
+ required this.overlayController,
+ });
+
+ @override
+ State createState() => _DraggableCardState();
+}
+
+class _DraggableCardState extends State {
+ final List _logs = [];
+ //
+ @override
+ void initState() {
+ super.initState();
+ analyticsService.instance.events.listen(_eventsListener);
+ }
+
+ void _eventsListener(event) {
+ if (!mounted) return;
+ _logs.add(
+ Text(
+ '=> $event',
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 12.0,
+ ),
+ ),
+ );
+ setState(() {});
+ }
+
+ @override
+ void dispose() {
+ widget.overlayController.remove();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final size = MediaQuery.of(context).size;
+
+ return Card(
+ elevation: 6.0,
+ color: Colors.black87,
+ clipBehavior: Clip.antiAlias,
+ child: SizedBox(
+ width: MediaQuery.of(context).size.width,
+ height: 200.0,
+ child: Row(
+ children: [
+ Expanded(
+ child: SizedBox(
+ height: 200.0,
+ child: ListView(
+ reverse: true,
+ padding: const EdgeInsets.all(6.0),
+ children: _logs.reversed.toList(),
+ ),
+ ),
+ ),
+ GestureDetector(
+ onPanUpdate: (details) {
+ widget.overlayController.alignChildTo(
+ details.globalPosition,
+ size * 0.5,
+ );
+ },
+ onPanEnd: (_) {
+ // widget.overlayController.alignToScreenEdge();
+ },
+ child: Container(
+ width: 25.0,
+ color: Colors.black,
+ child: const Center(
+ child: Icon(
+ Icons.drag_indicator_rounded,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class OverlayController extends AnimatedOverlay {
+ OverlayController(super.duration);
+ OverlayEntry? _entry;
+ final _defaultAlign = const Alignment(0.0, -1.8);
+ Alignment align = const Alignment(0.0, -1.8);
+ Animation? alignAnimation;
+
+ OverlayEntry createAlignOverlay(Widget child) {
+ return OverlayEntry(
+ maintainState: true,
+ builder: (_) => CustomAlign(
+ animation: alignAnimation ?? AlwaysStoppedAnimation(align),
+ child: child,
+ ),
+ );
+ }
+
+ void insert(BuildContext context) {
+ _entry = createAlignOverlay(DraggableCard(overlayController: this));
+ Overlay.of(context).insert(_entry!);
+ }
+
+ void show(BuildContext context) {
+ if (_entry != null) {
+ toggle();
+ } else {
+ insert(context);
+ }
+ }
+
+ void remove() {
+ _entry?.remove();
+ _entry?.dispose();
+ _entry = null;
+ }
+
+ void alignChildTo(Offset globalPosition, Size size) {
+ double dx = (globalPosition.dx - size.width) / size.width;
+ double dy = (globalPosition.dy - size.height) / size.height;
+
+ dx = dx.abs() < 1 ? dx : dx / dx.abs();
+ dy = dy.abs() < 1 ? dy : dy / dy.abs();
+
+ final newAlign = Alignment(dx, dy);
+
+ if (align == newAlign) return;
+
+ alignAnimation = createAnimation(begin: align, end: newAlign);
+
+ align = newAlign;
+
+ controller.forward();
+ _entry?.markNeedsBuild();
+ }
+
+ void toggle() {
+ if (align == Alignment.center) {
+ alignAnimation = createAnimation(
+ begin: align,
+ end: _defaultAlign,
+ );
+ align = _defaultAlign;
+ } else {
+ alignAnimation = createAnimation(
+ begin: align,
+ end: Alignment.center,
+ );
+ align = Alignment.center;
+ }
+
+ controller.forward();
+ _entry?.markNeedsBuild();
+ }
+}
+
+abstract class AnimatedOverlay extends TickerProvider {
+ @override
+ Ticker createTicker(onTick) => Ticker(onTick);
+
+ late final AnimationController controller;
+
+ AnimatedOverlay(Duration duration) : super() {
+ controller = AnimationController(
+ vsync: this,
+ duration: duration,
+ );
+ }
+
+ Animation createAnimation({
+ required T begin,
+ required T end,
+ Curve curve = Curves.easeInOutCubic,
+ }) {
+ controller.reset();
+ if (begin == end) {
+ return AlwaysStoppedAnimation(end);
+ } else {
+ return Tween(begin: begin, end: end).animate(
+ CurvedAnimation(
+ parent: controller,
+ curve: curve,
+ ),
+ );
+ }
+ }
+}
+
+class CustomAlign extends AnimatedWidget {
+ final Widget child;
+ final Animation animation;
+
+ const CustomAlign({
+ super.key,
+ required this.child,
+ required this.animation,
+ }) : super(listenable: animation);
+
+ @override
+ Widget build(BuildContext context) {
+ return Align(
+ alignment: animation.value,
+ child: child,
+ );
+ }
+}
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 3ebb6b9e..03439581 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import flutter_timezone
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
@@ -12,6 +13,7 @@ import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 2e43b6b5..9bf538bf 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -387,6 +387,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_timezone:
+ dependency: transitive
+ description:
+ name: flutter_timezone
+ sha256: "06b35132c98fa188db3c4b654b7e1af7ccd01dfe12a004d58be423357605fb24"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -1025,10 +1033,10 @@ packages:
dependency: transitive
description:
name: uuid
- sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921
+ sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
- version: "4.2.1"
+ version: "4.3.3"
vector_graphics:
dependency: transitive
description:
@@ -1107,7 +1115,7 @@ packages:
path: ".."
relative: true
source: path
- version: "3.1.2"
+ version: "3.1.3-beta01"
web_socket_channel:
dependency: transitive
description:
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index c58a5fe1..83e3155a 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -3,7 +3,7 @@ description: A dApp showing how to use WalletConnect v2 with Flutter
publish_to: "none"
-version: 3.1.2
+version: 3.1.3
environment:
sdk: ">=3.0.1 <4.0.0"
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0a76f782..91be99cf 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -8,6 +8,8 @@ PODS:
- CoinbaseWalletSDK/CrossPlatform (1.0.4):
- CoinbaseWalletSDK/Client
- Flutter (1.0.0)
+ - flutter_timezone (0.0.1):
+ - Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
@@ -29,6 +31,7 @@ DEPENDENCIES:
- appcheck (from `.symlinks/plugins/appcheck/ios`)
- coinbase_wallet_sdk (from `.symlinks/plugins/coinbase_wallet_sdk/ios`)
- Flutter (from `Flutter`)
+ - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
@@ -47,6 +50,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/coinbase_wallet_sdk/ios"
Flutter:
:path: Flutter
+ flutter_timezone:
+ :path: ".symlinks/plugins/flutter_timezone/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
@@ -63,6 +68,7 @@ SPEC CHECKSUMS:
coinbase_wallet_sdk: 7ccd4e1a7940deba6ba9bd81beece999a2268c15
CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ flutter_timezone: ffb07bdad3c6276af8dada0f11978d8a1f8a20bb
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
diff --git a/lib/models/w3m_chain_info.freezed.dart b/lib/models/w3m_chain_info.freezed.dart
index 9b2fce76..7b72b6cc 100644
--- a/lib/models/w3m_chain_info.freezed.dart
+++ b/lib/models/w3m_chain_info.freezed.dart
@@ -12,7 +12,7 @@ part of 'w3m_chain_info.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$W3MChainInfo {
@@ -219,7 +219,7 @@ class _$W3MChainInfoImpl implements _W3MChainInfo {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$W3MChainInfoImpl &&
@@ -380,7 +380,7 @@ class _$W3MBlockExplorerImpl implements _W3MBlockExplorer {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$W3MBlockExplorerImpl &&
diff --git a/lib/models/w3m_wallet_info.freezed.dart b/lib/models/w3m_wallet_info.freezed.dart
index 8cab6669..9089b705 100644
--- a/lib/models/w3m_wallet_info.freezed.dart
+++ b/lib/models/w3m_wallet_info.freezed.dart
@@ -12,7 +12,7 @@ part of 'w3m_wallet_info.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
W3MWalletInfo _$W3MWalletInfoFromJson(Map json) {
return _W3MWalletInfo.fromJson(json);
@@ -138,7 +138,7 @@ class _$W3MWalletInfoImpl implements _W3MWalletInfo {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$W3MWalletInfoImpl &&
diff --git a/lib/pages/about_wallets.dart b/lib/pages/about_wallets.dart
index 6ec4b977..6434cadc 100644
--- a/lib/pages/about_wallets.dart
+++ b/lib/pages/about_wallets.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:web3modal_flutter/pages/get_wallet_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/widgets/widget_stack/widget_stack_singleton.dart';
import 'package:web3modal_flutter/constants/key_constants.dart';
import 'package:web3modal_flutter/widgets/buttons/simple_icon_button.dart';
@@ -56,7 +57,10 @@ class AboutWallets extends StatelessWidget {
const SizedBox(height: 8),
SimpleIconButton(
onTap: () {
- widgetStack.instance.push(const GetWalletPage());
+ widgetStack.instance.push(
+ const GetWalletPage(),
+ event: ClickGetWalletEvent(),
+ );
},
leftIcon: 'assets/icons/wallet.svg',
title: 'Get a wallet',
diff --git a/lib/pages/account_page.dart b/lib/pages/account_page.dart
index e486bf66..7bb95109 100644
--- a/lib/pages/account_page.dart
+++ b/lib/pages/account_page.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:web3modal_flutter/constants/key_constants.dart';
import 'package:web3modal_flutter/pages/select_network_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/services/explorer_service/explorer_service_singleton.dart';
import 'package:web3modal_flutter/services/w3m_service/i_w3m_service.dart';
import 'package:web3modal_flutter/theme/constants.dart';
@@ -110,12 +111,15 @@ class _AccountPageState extends State with WidgetsBindingObserver {
),
title: _service?.selectedChain?.chainName ?? '',
onTap: () {
- widgetStack.instance.push(SelectNetworkPage(
- onTapNetwork: (W3MChainInfo chainInfo) {
- _service?.selectChain(chainInfo, switchChain: true);
- widgetStack.instance.pop();
- },
- ));
+ widgetStack.instance.push(
+ SelectNetworkPage(
+ onTapNetwork: (W3MChainInfo chainInfo) {
+ _service?.selectChain(chainInfo, switchChain: true);
+ widgetStack.instance.pop();
+ },
+ ),
+ event: ClickNetworksEvent(),
+ );
},
),
const SizedBox.square(dimension: kPadding8),
diff --git a/lib/pages/qr_code_page.dart b/lib/pages/qr_code_page.dart
index c980dd42..9aabf993 100644
--- a/lib/pages/qr_code_page.dart
+++ b/lib/pages/qr_code_page.dart
@@ -6,7 +6,7 @@ import 'package:web3modal_flutter/services/w3m_service/i_w3m_service.dart';
import 'package:web3modal_flutter/theme/constants.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
import 'package:web3modal_flutter/widgets/buttons/simple_icon_button.dart';
-import 'package:web3modal_flutter/widgets/w3m_qr_code.dart';
+import 'package:web3modal_flutter/widgets/qr_code_view.dart';
import 'package:web3modal_flutter/widgets/miscellaneous/responsive_container.dart';
import 'package:web3modal_flutter/widgets/web3modal_provider.dart';
import 'package:web3modal_flutter/widgets/navigation/navbar.dart';
@@ -40,7 +40,7 @@ class _QRCodePageState extends State {
}
void _buildWidget() => setState(() {
- _qrQodeWidget = QRCodeWidget(
+ _qrQodeWidget = QRCodeView(
uri: _service!.wcUri!,
logoPath: 'assets/png/logo_wc.png',
);
diff --git a/lib/pages/select_network_page.dart b/lib/pages/select_network_page.dart
index ab2ceed6..469993db 100644
--- a/lib/pages/select_network_page.dart
+++ b/lib/pages/select_network_page.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:web3modal_flutter/constants/key_constants.dart';
import 'package:web3modal_flutter/pages/about_networks.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/theme/constants.dart';
import 'package:web3modal_flutter/widgets/miscellaneous/responsive_container.dart';
import 'package:web3modal_flutter/widgets/widget_stack/widget_stack_singleton.dart';
@@ -14,8 +15,10 @@ import 'package:web3modal_flutter/widgets/navigation/navbar.dart';
import 'package:web3modal_flutter/widgets/web3modal_provider.dart';
class SelectNetworkPage extends StatelessWidget {
- const SelectNetworkPage({required this.onTapNetwork})
- : super(key: KeyConstants.selectNetworkPage);
+ const SelectNetworkPage({
+ required this.onTapNetwork,
+ }) : super(key: KeyConstants.selectNetworkPage);
+
final Function(W3MChainInfo)? onTapNetwork;
@override
@@ -63,7 +66,10 @@ class SelectNetworkPage extends StatelessWidget {
),
SimpleIconButton(
onTap: () {
- widgetStack.instance.push(const AboutNetworks());
+ widgetStack.instance.push(
+ const AboutNetworks(),
+ event: ClickNetworkHelpEvent(),
+ );
},
size: BaseButtonSize.small,
leftIcon: 'assets/icons/help.svg',
diff --git a/lib/pages/wallets_list_short_page.dart b/lib/pages/wallets_list_short_page.dart
index 7756cf1c..91ce39ea 100644
--- a/lib/pages/wallets_list_short_page.dart
+++ b/lib/pages/wallets_list_short_page.dart
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:web3modal_flutter/pages/about_wallets.dart';
import 'package:web3modal_flutter/pages/connect_wallet_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/services/explorer_service/explorer_service_singleton.dart';
import 'package:web3modal_flutter/theme/constants.dart';
import 'package:web3modal_flutter/widgets/widget_stack/widget_stack_singleton.dart';
@@ -39,7 +40,10 @@ class _WalletsListShortPageState extends State {
leftAction: NavbarActionButton(
asset: 'assets/icons/help.svg',
action: () {
- widgetStack.instance.push(const AboutWallets());
+ widgetStack.instance.push(
+ const AboutWallets(),
+ event: ClickWalletHelpEvent(),
+ );
},
),
safeAreaLeft: true,
@@ -80,8 +84,10 @@ class _WalletsListShortPageState extends State {
},
),
onTap: () {
- widgetStack.instance
- .push(const WalletsListLongPage());
+ widgetStack.instance.push(
+ const WalletsListLongPage(),
+ event: ClickAllWalletsEvent(),
+ );
},
),
],
diff --git a/lib/services/analytics_service/analytics_service.dart b/lib/services/analytics_service/analytics_service.dart
new file mode 100644
index 00000000..74a3a819
--- /dev/null
+++ b/lib/services/analytics_service/analytics_service.dart
@@ -0,0 +1,110 @@
+import 'dart:async';
+import 'dart:convert';
+import 'package:flutter/foundation.dart';
+import 'package:http/http.dart' as http;
+import 'package:uuid/uuid.dart';
+
+import 'package:web3modal_flutter/services/analytics_service/i_analytics_service.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
+import 'package:web3modal_flutter/services/logger_service/logger_service_singleton.dart';
+import 'package:web3modal_flutter/utils/core/core_utils_singleton.dart';
+import 'package:web3modal_flutter/web3modal_flutter.dart';
+
+class AnalyticsService implements IAnalyticsService {
+ static final _eventsController = StreamController.broadcast();
+ static const _debugApiEndpoint =
+ 'https://analytics-api-cf-workers-staging.walletconnect-v1-bridge.workers.dev';
+ static const _debugProjectId = 'e087b4b0503b860119be49d906717c12';
+ bool _isEnabled = false;
+
+ @override
+ final Stream events = _eventsController.stream;
+
+ @override
+ final String projectId;
+
+ @override
+ final bool? enableAnalytics;
+
+ AnalyticsService({
+ required this.projectId,
+ this.enableAnalytics,
+ });
+
+ @override
+ Future init() async {
+ try {
+ if (enableAnalytics == null) {
+ _isEnabled = await fetchAnalyticsConfig();
+ } else {
+ _isEnabled = enableAnalytics!;
+ }
+ loggerService.instance.i('[$runtimeType] init enabled: $_isEnabled');
+ } catch (e, s) {
+ loggerService.instance.e(
+ '[$runtimeType] init error',
+ error: e,
+ stackTrace: s,
+ );
+ }
+ }
+
+ @override
+ Future fetchAnalyticsConfig() async {
+ try {
+ final endpoint = await coreUtils.instance.getApiUrl();
+ final headers = coreUtils.instance.getAPIHeaders(projectId);
+ final response = await http.get(
+ Uri.parse('$endpoint/getAnalyticsConfig'),
+ headers: headers,
+ );
+ final json = jsonDecode(response.body) as Map;
+ final enabled = json['isAnalyticsEnabled'] as bool?;
+ loggerService.instance.i('[$runtimeType] fetchAnalyticsConfig $enabled');
+ return enabled ?? false;
+ } catch (e, s) {
+ loggerService.instance.e(
+ '[$runtimeType] fetchAnalyticsConfig error',
+ error: e,
+ stackTrace: s,
+ );
+ return false;
+ }
+ }
+
+ @override
+ void sendEvent(AnalyticsEvent analyticsEvent) async {
+ if (!_isEnabled) return;
+ try {
+ final endpoint = kDebugMode
+ ? _debugApiEndpoint
+ : await coreUtils.instance.getAnalyticsUrl();
+ final headers = kDebugMode
+ ? coreUtils.instance.getAPIHeaders(_debugProjectId)
+ : coreUtils.instance.getAPIHeaders(projectId);
+
+ final packageName = await WalletConnectUtils.getPackageName();
+ final body = jsonEncode({
+ 'eventId': Uuid().v4(),
+ 'bundleId': packageName,
+ 'timestamp': DateTime.now().toUtc().millisecondsSinceEpoch,
+ 'props': analyticsEvent.toMap(),
+ });
+
+ final response = await http.post(
+ Uri.parse('$endpoint/e'),
+ headers: headers,
+ body: body,
+ );
+ final code = response.statusCode;
+ loggerService.instance.i('[$runtimeType] sendEvent ::$body:: $code');
+ _eventsController.sink.add(analyticsEvent.toMap());
+ } catch (e, s) {
+ loggerService.instance.e(
+ '[$runtimeType] sendEvent error',
+ error: e,
+ stackTrace: s,
+ );
+ }
+ }
+}
diff --git a/lib/services/analytics_service/analytics_service_singleton.dart b/lib/services/analytics_service/analytics_service_singleton.dart
new file mode 100644
index 00000000..ffdc01ce
--- /dev/null
+++ b/lib/services/analytics_service/analytics_service_singleton.dart
@@ -0,0 +1,7 @@
+import 'package:web3modal_flutter/services/analytics_service/i_analytics_service.dart';
+
+class AnalyticsServiceSingleton {
+ late IAnalyticsService instance;
+}
+
+final analyticsService = AnalyticsServiceSingleton();
diff --git a/lib/services/analytics_service/i_analytics_service.dart b/lib/services/analytics_service/i_analytics_service.dart
new file mode 100644
index 00000000..39c090aa
--- /dev/null
+++ b/lib/services/analytics_service/i_analytics_service.dart
@@ -0,0 +1,10 @@
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
+
+abstract class IAnalyticsService {
+ String get projectId;
+ bool? get enableAnalytics;
+ Stream get events;
+ Future init();
+ void sendEvent(AnalyticsEvent analyticsEvent);
+ Future fetchAnalyticsConfig();
+}
diff --git a/lib/services/analytics_service/models/analytics_event.dart b/lib/services/analytics_service/models/analytics_event.dart
new file mode 100644
index 00000000..32d7d908
--- /dev/null
+++ b/lib/services/analytics_service/models/analytics_event.dart
@@ -0,0 +1,335 @@
+enum AnalyticsPlatform {
+ mobile,
+ web,
+ qrcode,
+ email,
+ unsupported,
+}
+
+abstract class AnalyticsEvent {
+ abstract final String type;
+ abstract final String event;
+ abstract final Map? properties;
+
+ Map toMap();
+}
+
+class ModalCreatedEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'MODAL_CREATED';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ModalLoadedEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'MODAL_LOADED';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ModalOpenEvent implements AnalyticsEvent {
+ final bool _connected;
+ ModalOpenEvent({
+ required bool connected,
+ }) : _connected = connected;
+
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'MODAL_OPEN';
+
+ @override
+ Map? get properties => {
+ 'connected': _connected,
+ };
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ModalCloseEvent implements AnalyticsEvent {
+ final bool _connected;
+ ModalCloseEvent({
+ required bool connected,
+ }) : _connected = connected;
+
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'MODAL_CLOSE';
+
+ @override
+ Map? get properties => {
+ 'connected': _connected,
+ };
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ClickAllWalletsEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CLICK_ALL_WALLETS';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ClickNetworksEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CLICK_NETWORKS';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class SwitchNetworkEvent implements AnalyticsEvent {
+ final String _network;
+ SwitchNetworkEvent({
+ required String network,
+ }) : _network = network;
+
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'SWITCH_NETWORK';
+
+ @override
+ Map? get properties => {
+ 'network': _network,
+ };
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class SelectWalletEvent implements AnalyticsEvent {
+ final String _name;
+ final String? _platform;
+ SelectWalletEvent({
+ required String name,
+ AnalyticsPlatform? platform,
+ }) : _name = name,
+ _platform = platform?.name;
+
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'SELECT_WALLET';
+
+ @override
+ Map? get properties => {
+ 'name': _name,
+ if (_platform != null) 'platform': _platform,
+ };
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ConnectSuccessEvent implements AnalyticsEvent {
+ final String _name;
+ final String? _method;
+ ConnectSuccessEvent({
+ required String name,
+ AnalyticsPlatform? method,
+ }) : _name = name,
+ _method = method?.name;
+
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CONNECT_SUCCESS';
+
+ @override
+ Map? get properties => {
+ 'name': _name,
+ if (_method != null) 'method': _method,
+ };
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ConnectErrorEvent implements AnalyticsEvent {
+ final String _message;
+ ConnectErrorEvent({
+ required String message,
+ }) : _message = message;
+
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CONNECT_ERROR';
+
+ @override
+ Map? get properties => {
+ 'message': _message,
+ };
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class DisconnectSuccessEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'DISCONNECT_SUCCESS';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class DisconnectErrorEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'DISCONNECT_ERROR';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ClickWalletHelpEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CLICK_WALLET_HELP';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ClickNetworkHelpEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CLICK_NETWORK_HELP';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
+
+class ClickGetWalletEvent implements AnalyticsEvent {
+ @override
+ String get type => 'track';
+
+ @override
+ String get event => 'CLICK_GET_WALLET';
+
+ @override
+ Map? get properties => null;
+
+ @override
+ Map toMap() => {
+ 'type': type,
+ 'event': event,
+ if (properties != null) 'properties': properties,
+ };
+}
diff --git a/lib/services/blockchain_api_service/blockchain_api_utils.dart b/lib/services/blockchain_api_service/blockchain_api_utils.dart
index 618fb476..0edd54aa 100644
--- a/lib/services/blockchain_api_service/blockchain_api_utils.dart
+++ b/lib/services/blockchain_api_service/blockchain_api_utils.dart
@@ -4,32 +4,26 @@ import 'package:http/http.dart' as http;
import 'package:web3modal_flutter/constants/string_constants.dart';
import 'package:web3modal_flutter/services/blockchain_api_service/blockchain_identity.dart';
import 'package:web3modal_flutter/services/blockchain_api_service/i_blockchain_api_utils.dart';
+import 'package:web3modal_flutter/utils/core/core_utils_singleton.dart';
class BlockchainApiUtils extends IBlockchainApiUtils {
- @override
- final String blockchainApiUriRoot;
-
+ //
@override
final String projectId;
- BlockchainApiUtils({
- this.blockchainApiUriRoot = 'https://rpc.walletconnect.com',
- required this.projectId,
- });
+ BlockchainApiUtils({required this.projectId});
@override
Future getIdentity(String address, int chainId) async {
final scope = '${StringConstants.namespace}:$chainId';
- final endpoint = '$blockchainApiUriRoot/v1/identity/$address'
- '?chainId=$scope&projectId=$projectId';
-
+ final url = await coreUtils.instance.getBlockchainApiUrl();
+ final endpoint =
+ '$url/v1/identity/$address?chainId=$scope&projectId=$projectId';
final response = await http.get(Uri.parse(endpoint));
if (response.statusCode == 200) {
- return BlockchainIdentity.fromJson(
- jsonDecode(response.body),
- );
+ return BlockchainIdentity.fromJson(jsonDecode(response.body));
} else {
- throw Exception('Failed to load data');
+ throw Exception('Failed to load avatar');
}
}
}
diff --git a/lib/services/blockchain_api_service/blockchain_identity.freezed.dart b/lib/services/blockchain_api_service/blockchain_identity.freezed.dart
index 8d15e42a..1c372f97 100644
--- a/lib/services/blockchain_api_service/blockchain_identity.freezed.dart
+++ b/lib/services/blockchain_api_service/blockchain_identity.freezed.dart
@@ -12,7 +12,7 @@ part of 'blockchain_identity.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
BlockchainIdentity _$BlockchainIdentityFromJson(Map json) {
return _BlockchainIdentity.fromJson(json);
@@ -124,7 +124,7 @@ class _$BlockchainIdentityImpl implements _BlockchainIdentity {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$BlockchainIdentityImpl &&
diff --git a/lib/services/blockchain_api_service/i_blockchain_api_utils.dart b/lib/services/blockchain_api_service/i_blockchain_api_utils.dart
index 98c35ac7..b61c4f4a 100644
--- a/lib/services/blockchain_api_service/i_blockchain_api_utils.dart
+++ b/lib/services/blockchain_api_service/i_blockchain_api_utils.dart
@@ -1,17 +1,9 @@
import 'package:web3modal_flutter/services/blockchain_api_service/blockchain_identity.dart';
abstract class IBlockchainApiUtils {
- static const blockchainApiEndpoint = 'https://rpc.walletconnect.com';
-
- /// The root URI of the blockchain API.
- String get blockchainApiUriRoot;
-
/// The project ID used when querying the API.
String get projectId;
/// Gets the name and avatar of a provided address on the given chain
- Future getIdentity(
- String address,
- int chainId,
- );
+ Future getIdentity(String address, int chainId);
}
diff --git a/lib/services/explorer_service/explorer_service.dart b/lib/services/explorer_service/explorer_service.dart
index f9970017..c8feb4eb 100644
--- a/lib/services/explorer_service/explorer_service.dart
+++ b/lib/services/explorer_service/explorer_service.dart
@@ -17,12 +17,13 @@ import 'package:web3modal_flutter/services/storage_service/storage_service_singl
import 'package:web3modal_flutter/utils/core/core_utils_singleton.dart';
import 'package:web3modal_flutter/utils/platform/i_platform_utils.dart';
import 'package:web3modal_flutter/utils/platform/platform_utils_singleton.dart';
+import 'package:web3modal_flutter/utils/w3m_logger.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
const int _defaultEntriesCount = 48;
class ExplorerService implements IExplorerService {
- static const _apiUrl = 'https://api.web3modal.com';
+ String? _apiUrl;
final http.Client _client;
final String _referer;
@@ -103,6 +104,7 @@ class ExplorerService implements IExplorerService {
}
W3MLoggerUtil.logger.t('[$runtimeType] init()');
+ _apiUrl = await coreUtils.instance.getApiUrl();
await _setInstalledWalletIdsParam();
await _fetchInitialWallets();
@@ -195,10 +197,11 @@ class ExplorerService implements IExplorerService {
Future> _fetchNativeAppData() async {
try {
+ final apiUrl = await coreUtils.instance.getApiUrl();
final headers = coreUtils.instance.getAPIHeaders(projectId, _referer);
final uri = Platform.isIOS
- ? Uri.parse('$_apiUrl/getIosData')
- : Uri.parse('$_apiUrl/getAndroidData');
+ ? Uri.parse('$apiUrl/getIosData')
+ : Uri.parse('$apiUrl/getAndroidData');
final response = await _client.get(uri, headers: headers);
final apiResponse = ApiResponse.fromJson(
jsonDecode(response.body),
@@ -265,8 +268,9 @@ class ExplorerService implements IExplorerService {
}) async {
final p = params?.toJson() ?? {};
try {
+ final apiUrl = await coreUtils.instance.getApiUrl();
final headers = coreUtils.instance.getAPIHeaders(projectId, _referer);
- final uri = Uri.parse('$_apiUrl/getWallets').replace(queryParameters: p);
+ final uri = Uri.parse('$apiUrl/getWallets').replace(queryParameters: p);
final response = await _client.get(uri, headers: headers);
final apiResponse = ApiResponse.fromJson(
jsonDecode(response.body),
@@ -427,7 +431,8 @@ class ExplorerService implements IExplorerService {
if (imageId.startsWith('http')) {
return imageId;
}
- return '$_apiUrl/getWalletImage/$imageId';
+ final apiUrl = _apiUrl ?? 'https://api.web3modal.com';
+ return '$apiUrl/getWalletImage/$imageId';
}
@override
@@ -435,7 +440,8 @@ class ExplorerService implements IExplorerService {
if (imageId.startsWith('http')) {
return imageId;
}
- return '$_apiUrl/public/getAssetImage/$imageId';
+ final apiUrl = _apiUrl ?? 'https://api.web3modal.com';
+ return '$apiUrl/public/getAssetImage/$imageId';
}
@override
diff --git a/lib/services/ledger_service/ledger_service.dart b/lib/services/ledger_service/ledger_service.dart
index 197c638b..ef50ac4b 100644
--- a/lib/services/ledger_service/ledger_service.dart
+++ b/lib/services/ledger_service/ledger_service.dart
@@ -1,5 +1,6 @@
import 'package:http/http.dart';
import 'package:web3modal_flutter/services/ledger_service/i_ledger_service.dart';
+import 'package:web3modal_flutter/services/logger_service/logger_service_singleton.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
class LedgerService extends ILedgerService {
@@ -10,7 +11,7 @@ class LedgerService extends ILedgerService {
final amount = await client.getBalance(EthereumAddress.fromHex(address));
return amount.getValueInUnit(EtherUnit.ether);
} catch (e, s) {
- W3MLoggerUtil.logger.e(
+ loggerService.instance.e(
'[$runtimeType] getBalance error',
error: e,
stackTrace: s,
diff --git a/lib/services/logger_service/i_logger_service.dart b/lib/services/logger_service/i_logger_service.dart
new file mode 100644
index 00000000..ce760de0
--- /dev/null
+++ b/lib/services/logger_service/i_logger_service.dart
@@ -0,0 +1,63 @@
+import 'package:web3modal_flutter/web3modal_flutter.dart';
+
+abstract class ILoggerService {
+ /// Log a message at level [Level.trace].
+ void t(
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Log a message at level [Level.debug].
+ void d(
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Log a message at level [Level.info].
+ void i(
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Log a message at level [Level.warning].
+ void w(
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Log a message at level [Level.error].
+ void e(
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Log a message at level [Level.fatal].
+ void f(
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Log a message with [level].
+ void log(
+ Level level,
+ dynamic message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ });
+
+ /// Closes the logger and releases all resources.
+ Future close();
+}
diff --git a/lib/services/logger_service/logger_service.dart b/lib/services/logger_service/logger_service.dart
new file mode 100644
index 00000000..584d9b79
--- /dev/null
+++ b/lib/services/logger_service/logger_service.dart
@@ -0,0 +1,100 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:web3modal_flutter/services/logger_service/i_logger_service.dart';
+import 'package:web3modal_flutter/web3modal_flutter.dart';
+
+class LoggerService implements ILoggerService {
+ late Logger _logger;
+ LoggerService({required LogLevel level, bool debugMode = true}) {
+ _logger = Logger(
+ level: level.toLevel(),
+ printer: PrettyPrinter(methodCount: null),
+ );
+ if (kDebugMode && debugMode) {
+ Logger.addLogListener(_logListener);
+ }
+ }
+
+ void _logListener(LogEvent event) {
+ // debugPrint('${event.message}');
+ }
+
+ @override
+ void d(
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.d(message, time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ void e(
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.e(message, time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ void f(
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.f(message, time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ void i(
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.i(message, time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ void log(
+ Level level,
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.log(level, message,
+ time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ void t(
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.t(message, time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ void w(
+ message, {
+ DateTime? time,
+ Object? error,
+ StackTrace? stackTrace,
+ }) {
+ _logger.w(message, time: time, error: error, stackTrace: stackTrace);
+ }
+
+ @override
+ Future close() async {
+ Logger.removeLogListener(_logListener);
+ return await _logger.close();
+ }
+}
diff --git a/lib/services/logger_service/logger_service_singleton.dart b/lib/services/logger_service/logger_service_singleton.dart
new file mode 100644
index 00000000..d21c03b2
--- /dev/null
+++ b/lib/services/logger_service/logger_service_singleton.dart
@@ -0,0 +1,7 @@
+import 'package:web3modal_flutter/services/logger_service/i_logger_service.dart';
+
+class LoggerServiceSingleton {
+ late ILoggerService instance;
+}
+
+final loggerService = LoggerServiceSingleton();
diff --git a/lib/services/w3m_service/w3m_service.dart b/lib/services/w3m_service/w3m_service.dart
index 559cee93..f84b40f7 100644
--- a/lib/services/w3m_service/w3m_service.dart
+++ b/lib/services/w3m_service/w3m_service.dart
@@ -7,16 +7,23 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:web3modal_flutter/constants/string_constants.dart';
import 'package:web3modal_flutter/pages/account_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/analytics_service.dart';
+import 'package:web3modal_flutter/services/analytics_service/analytics_service_singleton.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/services/coinbase_service/coinbase_service.dart';
import 'package:web3modal_flutter/services/coinbase_service/i_coinbase_service.dart';
import 'package:web3modal_flutter/services/coinbase_service/models/coinbase_data.dart';
import 'package:web3modal_flutter/services/coinbase_service/models/coinbase_events.dart';
import 'package:web3modal_flutter/services/explorer_service/explorer_service.dart';
import 'package:web3modal_flutter/services/explorer_service/explorer_service_singleton.dart';
+import 'package:web3modal_flutter/services/explorer_service/models/redirect.dart';
import 'package:web3modal_flutter/services/ledger_service/ledger_service_singleton.dart';
+import 'package:web3modal_flutter/services/logger_service/logger_service.dart';
+import 'package:web3modal_flutter/services/logger_service/logger_service_singleton.dart';
import 'package:web3modal_flutter/utils/core/core_utils_singleton.dart';
import 'package:web3modal_flutter/utils/platform/i_platform_utils.dart';
import 'package:web3modal_flutter/utils/url/launch_url_exception.dart';
+import 'package:web3modal_flutter/utils/w3m_logger.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
import 'package:web3modal_flutter/widgets/widget_stack/widget_stack_singleton.dart';
import 'package:web3modal_flutter/services/blockchain_api_service/blockchain_api_utils.dart';
@@ -93,6 +100,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
Set? featuredWalletIds,
Set? includedWalletIds,
Set? excludedWalletIds,
+ bool? enableAnalytics,
LogLevel logLevel = LogLevel.nothing,
}) {
if (web3App == null) {
@@ -102,10 +110,16 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
);
}
if (metadata == null) {
- throw ArgumentError('Metada is required when using projectId.');
+ throw ArgumentError(
+ 'Metada is required when using projectId.',
+ );
}
}
+ loggerService.instance = LoggerService(level: logLevel, debugMode: true);
+
+ W3MLoggerUtil.setLogLevel(logLevel, debugMode: true);
+
_web3App = web3App ??
Web3App(
core: Core(projectId: projectId!),
@@ -117,6 +131,13 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
_setOptionalNamespaces(optionalNamespaces);
+ analyticsService.instance = AnalyticsService(
+ projectId: _projectId,
+ enableAnalytics: enableAnalytics,
+ )..init().then((_) {
+ analyticsService.instance.sendEvent(ModalLoadedEvent());
+ });
+
explorerService.instance = ExplorerService(
projectId: _projectId,
referer: _web3App.metadata.name.replaceAll(' ', ''),
@@ -128,8 +149,6 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
blockchainApiUtils.instance = BlockchainApiUtils(
projectId: _projectId,
);
-
- W3MLoggerUtil.setLogLevel(logLevel, debugMode: true);
}
////////* PUBLIC METHODS */////////
@@ -154,6 +173,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
await storageService.instance.init();
await networkService.instance.init();
await explorerService.instance.init();
+ // await analyticsService.instance.init();
if (_initializeCoinbaseSDK) {
// final isInstalled = await cbIsInstalled();
// Fetch Coinbase Wallet object to get updated metadata
@@ -237,10 +257,10 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
defaultValue: '',
)!;
if (chainId.isNotEmpty && W3MChainPresets.chains.containsKey(chainId)) {
- await selectChain(W3MChainPresets.chains[chainId]!);
+ await selectChain(W3MChainPresets.chains[chainId]!, logEvent: false);
} else {
final chainId = _currentSession!.chainId;
- await selectChain(W3MChainPresets.chains[chainId]!);
+ await selectChain(W3MChainPresets.chains[chainId]!, logEvent: false);
}
}
}
@@ -249,6 +269,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
Future selectChain(
W3MChainInfo? chainInfo, {
bool switchChain = false,
+ bool logEvent = true,
}) async {
_checkInitialized();
@@ -275,10 +296,10 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
await launchConnectedWallet();
}
} else {
- _setEthChain(chainInfo);
+ _setEthChain(chainInfo, logEvent: logEvent);
}
} else {
- _setEthChain(chainInfo);
+ _setEthChain(chainInfo, logEvent: logEvent);
}
}
@@ -317,7 +338,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
return _currentSession!.getApprovedEvents();
}
- void _setEthChain(W3MChainInfo chainInfo) async {
+ void _setEthChain(W3MChainInfo chainInfo, {bool logEvent = false}) async {
W3MLoggerUtil.logger.t('[$runtimeType] set chain ${chainInfo.namespace}');
_currentSelectedChain = chainInfo;
@@ -327,6 +348,10 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
StringConstants.selectedChainId,
_currentSelectedChain!.chainId,
);
+ if (_isConnected) {
+ final network = chainInfo.chainId;
+ analyticsService.instance.sendEvent(SwitchNetworkEvent(network: network));
+ }
_notify();
_loadAccountData();
@@ -341,6 +366,10 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
}
_isOpen = true;
+ analyticsService.instance.sendEvent(ModalOpenEvent(
+ connected: _isConnected,
+ ));
+
// Reset the explorer
explorerService.instance.search(query: null);
widgetStack.instance.clear();
@@ -386,12 +415,14 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
context: _context!,
builder: (_) => rootWidget,
);
+ _close();
} else {
await showDialog(
useRootNavigator: true,
context: _context!,
builder: (_) => rootWidget,
);
+ _close();
}
_isOpen = false;
@@ -407,18 +438,44 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
}
}
+ void _trackSelectedWallet(
+ WalletRedirect? walletRedirect, {
+ bool inBrowser = false,
+ }) {
+ final walletName = _selectedWallet!.listing.name;
+ final event = SelectWalletEvent(
+ name: walletName,
+ platform: inBrowser ? AnalyticsPlatform.web : AnalyticsPlatform.mobile,
+ );
+ // if (walletRedirect?.mobileOnly == true) {
+ // event = SelectWalletEvent(
+ // name: walletName,
+ // platform: AnalyticsPlatform.mobile.name,
+ // );
+ // }
+ // if (walletRedirect?.webOnly == true) {
+ // event = SelectWalletEvent(
+ // name: walletName,
+ // platform: AnalyticsPlatform.web.name,
+ // );
+ // }
+ analyticsService.instance.sendEvent(event);
+ }
+
@override
Future connectSelectedWallet({bool inBrowser = false}) async {
_checkInitialized();
- final selectedWalletRedirect = explorerService.instance.getWalletRedirect(
+ final walletRedirect = explorerService.instance.getWalletRedirect(
selectedWallet,
);
- if (selectedWalletRedirect == null) {
+ if (walletRedirect == null) {
throw W3MServiceException(
'You didn\'t select a wallet or walletInfo argument is null',
);
}
+ _trackSelectedWallet(walletRedirect, inBrowser: inBrowser);
+
var pType = platformUtils.instance.getPlatformType();
if (inBrowser) {
pType = PlatformType.web;
@@ -430,7 +487,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
} else {
await buildConnectionUri();
await urlUtils.instance.openRedirect(
- selectedWalletRedirect,
+ walletRedirect,
wcURI: wcUri!,
pType: pType,
);
@@ -456,13 +513,22 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
if (_isUserRejectedError(e)) {
W3MLoggerUtil.logger.t('[$runtimeType] User declined connection');
onWalletConnectionError.broadcast(UserRejectedConnection());
+ analyticsService.instance.sendEvent(ConnectErrorEvent(
+ message: 'User declined connection',
+ ));
} else {
onWalletConnectionError.broadcast(ErrorOpeningWallet());
+ analyticsService.instance.sendEvent(ConnectErrorEvent(
+ message: e.message,
+ ));
}
}
} else if (_isUserRejectedError(e)) {
W3MLoggerUtil.logger.t('[$runtimeType] User declined connection');
onWalletConnectionError.broadcast(UserRejectedConnection());
+ analyticsService.instance.sendEvent(ConnectErrorEvent(
+ message: 'User declined connection',
+ ));
} else {
W3MLoggerUtil.logger.e(
'[$runtimeType] Error connecting wallet',
@@ -507,12 +573,19 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
if (_isUserRejectedError(e)) {
W3MLoggerUtil.logger.t('[$runtimeType] User declined connection');
onWalletConnectionError.broadcast(UserRejectedConnection());
+ analyticsService.instance.sendEvent(ConnectErrorEvent(
+ message: 'User declined connection',
+ ));
} else {
+ final message = e.message ?? 'Error connecting to wallet';
W3MLoggerUtil.logger.e(
- '[$runtimeType] Error connecting to wallet',
+ '[$runtimeType] $message',
error: e,
stackTrace: s,
);
+ analyticsService.instance.sendEvent(ConnectErrorEvent(
+ message: message,
+ ));
}
return await expirePreviousInactivePairings();
}
@@ -558,39 +631,56 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
Future disconnect({bool disconnectAllSessions = true}) async {
_checkInitialized();
- // If we want to disconnect all sessions, loop through them and disconnect them
- if (disconnectAllSessions) {
- for (final SessionData session in _web3App.sessions.getAll()) {
- await _disconnectSession(session.pairingTopic, session.topic);
+ try {
+ // If we want to disconnect all sessions, loop through them and disconnect them
+ if (disconnectAllSessions) {
+ for (final SessionData session in _web3App.sessions.getAll()) {
+ await _disconnectSession(session.pairingTopic, session.topic);
+ }
+ } else {
+ // Disconnect the session
+ await _disconnectSession(
+ _currentSession?.pairingTopic,
+ _currentSession?.topic,
+ );
}
- } else {
- // Disconnect the session
- await _disconnectSession(
- _currentSession?.pairingTopic,
- _currentSession?.topic,
- );
- }
- return await _cleanSession();
+ analyticsService.instance.sendEvent(DisconnectSuccessEvent());
+ return await _cleanSession();
+ } catch (e) {
+ analyticsService.instance.sendEvent(DisconnectErrorEvent());
+ }
}
@override
void closeModal() {
// If we aren't open, then we can't and shouldn't close
- if (!_isOpen) {
- return;
- }
-
- toastUtils.instance.clear();
+ _close(event: false);
if (_context != null) {
// _isOpen and notify() are handled when we call Navigator.pop()
// by the open() method
Navigator.of(_context!, rootNavigator: true).pop();
+ analyticsService.instance.sendEvent(ModalCloseEvent(
+ connected: _isConnected,
+ ));
} else {
_notify();
}
}
+ void _close({bool event = true}) {
+ if (!_isOpen) {
+ return;
+ }
+ _isOpen = false;
+ toastUtils.instance.clear();
+ if (event) {
+ analyticsService.instance.sendEvent(ModalCloseEvent(
+ connected: _isConnected,
+ ));
+ }
+ }
+
@override
void selectWallet(W3MWalletInfo walletInfo) {
_selectedWallet = walletInfo;
@@ -679,6 +769,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
);
} catch (e) {
if (_isUserRejectedError(e)) {
+ W3MLoggerUtil.logger.t('[$runtimeType] User declined request');
onWalletConnectionError.broadcast(UserRejectedConnection());
if (request.method == MethodsConstants.walletSwitchEthChain ||
request.method == MethodsConstants.walletAddEthChain) {
@@ -698,9 +789,13 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
}
@override
- void dispose() {
+ void dispose() async {
if (_status == W3MServiceStatus.initialized) {
+ await disconnect();
+ await expirePreviousInactivePairings();
_unregisterListeners();
+ _status = W3MServiceStatus.idle;
+ loggerService.instance.d('[$runtimeType] dispose');
}
super.dispose();
}
@@ -824,11 +919,9 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
int.parse(_currentSelectedChain!.chainId),
);
_avatarUrl = blockchainId.avatar;
- } catch (_) {
- W3MLoggerUtil.logger
- .e('[$runtimeType] Couldn\'t load avatar, will use default icon');
+ } catch (e) {
+ W3MLoggerUtil.logger.e('[$runtimeType] $e');
}
- W3MLoggerUtil.logger.t('[$runtimeType] account data laoded');
_notify();
}
@@ -847,7 +940,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
],
),
).then((_) {
- _setEthChain(newChain);
+ _setEthChain(newChain, logEvent: true);
}).catchError(
(e, s) {
// if request errors due to user rejection then set the previous chain
@@ -864,7 +957,7 @@ class W3MService with ChangeNotifier, CoinbaseService implements IW3MService {
params: [newChain.toJson()],
),
).then((_) {
- _setEthChain(newChain);
+ _setEthChain(newChain, logEvent: true);
}).catchError((_) {
_setEthChain(_currentSelectedChain!);
});
@@ -1035,9 +1128,24 @@ extension _W3MServiceExtension on W3MService {
await _storeSession(session);
onModalConnect.broadcast(ModalConnect(session));
if (_selectedWallet == null) {
- await storageService.instance.clearKey(StringConstants.recentWalletId);
- await storageService.instance
- .clearKey(StringConstants.connectedWalletData);
+ // final walletName = args.session.peer.metadata.name;
+ analyticsService.instance.sendEvent(ConnectSuccessEvent(
+ name: 'WalletConnect',
+ method: AnalyticsPlatform.qrcode,
+ ));
+ await storageService.instance.clearKey(
+ StringConstants.recentWalletId,
+ );
+ await storageService.instance.clearKey(
+ StringConstants.connectedWalletData,
+ );
+ } else {
+ // TODO check this logic
+ final walletName = _selectedWallet!.listing.name;
+ analyticsService.instance.sendEvent(ConnectSuccessEvent(
+ name: walletName,
+ method: AnalyticsPlatform.mobile,
+ ));
}
await _selectChainFromStoredId();
_loadAccountData();
diff --git a/lib/theme/w3m_colors.freezed.dart b/lib/theme/w3m_colors.freezed.dart
index de436543..1dcf9d03 100644
--- a/lib/theme/w3m_colors.freezed.dart
+++ b/lib/theme/w3m_colors.freezed.dart
@@ -12,7 +12,7 @@ part of 'w3m_colors.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$Web3ModalColors {
@@ -606,7 +606,7 @@ class _$Web3ModalColorsImpl implements _Web3ModalColors {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Web3ModalColorsImpl &&
diff --git a/lib/theme/w3m_radiuses.freezed.dart b/lib/theme/w3m_radiuses.freezed.dart
index 4a3ec825..05d2c082 100644
--- a/lib/theme/w3m_radiuses.freezed.dart
+++ b/lib/theme/w3m_radiuses.freezed.dart
@@ -12,7 +12,7 @@ part of 'w3m_radiuses.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$Web3ModalRadiuses {
@@ -226,7 +226,7 @@ class _$Web3ModalRadiusesImpl implements _Web3ModalRadiuses {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Web3ModalRadiusesImpl &&
diff --git a/lib/theme/w3m_text_styles.freezed.dart b/lib/theme/w3m_text_styles.freezed.dart
index ba3dae5f..f436b7a3 100644
--- a/lib/theme/w3m_text_styles.freezed.dart
+++ b/lib/theme/w3m_text_styles.freezed.dart
@@ -12,7 +12,7 @@ part of 'w3m_text_styles.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$Web3ModalTextStyles {
@@ -378,7 +378,7 @@ class _$Web3ModalTextStylesImpl implements _Web3ModalTextStyles {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Web3ModalTextStylesImpl &&
diff --git a/lib/theme/w3m_theme_data.freezed.dart b/lib/theme/w3m_theme_data.freezed.dart
index 96df6db7..22243e91 100644
--- a/lib/theme/w3m_theme_data.freezed.dart
+++ b/lib/theme/w3m_theme_data.freezed.dart
@@ -12,7 +12,7 @@ part of 'w3m_theme_data.dart';
T _$identity(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
- 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$Web3ModalThemeData {
@@ -204,7 +204,7 @@ class _$Web3ModalThemeDataImpl implements _Web3ModalThemeData {
}
@override
- bool operator ==(dynamic other) {
+ bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Web3ModalThemeDataImpl &&
diff --git a/lib/utils/core/core_utils.dart b/lib/utils/core/core_utils.dart
index eb20899a..9c30441c 100644
--- a/lib/utils/core/core_utils.dart
+++ b/lib/utils/core/core_utils.dart
@@ -1,14 +1,66 @@
+import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
import 'package:web3modal_flutter/constants/string_constants.dart';
import 'package:web3modal_flutter/utils/core/i_core_utils.dart';
import 'package:web3modal_flutter/utils/w3m_logger.dart';
class CoreUtils extends ICoreUtils {
+ static const restrictedTimezone = [
+ 'ASIA/SHANGHAI',
+ 'ASIA/URUMQI',
+ 'ASIA/CHONGQING',
+ 'ASIA/HARBIN',
+ 'ASIA/KASHGAR',
+ 'ASIA/MACAU',
+ 'ASIA/HONG_KONG',
+ 'ASIA/MACAO',
+ 'ASIA/BEIJING',
+ 'ASIA/HARBIN',
+ ];
+
@override
bool isValidProjectID(String projectId) {
return RegExp(r'^[0-9a-fA-F]{32}$').hasMatch(projectId);
}
+ @override
+ Future isRestrictedRegion() async {
+ try {
+ String tz = await FlutterTimezone.getLocalTimezone();
+ tz = tz.toUpperCase();
+ return restrictedTimezone.contains(tz);
+ } catch (e) {
+ return false;
+ }
+ }
+
+ @override
+ Future getApiUrl() async {
+ final restricted = await isRestrictedRegion();
+ if (restricted) {
+ return 'https://api.web3modal.org';
+ }
+ return 'https://api.web3modal.com';
+ }
+
+ @override
+ Future getBlockchainApiUrl() async {
+ final restricted = await isRestrictedRegion();
+ if (restricted) {
+ return 'https://rpc.walletconnect.org';
+ }
+ return 'https://rpc.walletconnect.com';
+ }
+
+ @override
+ Future getAnalyticsUrl() async {
+ final restricted = await isRestrictedRegion();
+ if (restricted) {
+ return 'https://pulse.walletconnect.org';
+ }
+ return 'https://pulse.walletconnect.com';
+ }
+
@override
bool isHttpUrl(String url) {
return url.startsWith('http://') || url.startsWith('https://');
@@ -99,7 +151,7 @@ class CoreUtils extends ICoreUtils {
'x-sdk-type': StringConstants.X_SDK_TYPE,
'x-sdk-version': 'flutter-${StringConstants.X_SDK_VERSION}',
'user-agent': getUserAgent(),
- 'referer': referer ?? '',
+ if (referer != null) 'referer': referer,
};
}
}
diff --git a/lib/utils/core/i_core_utils.dart b/lib/utils/core/i_core_utils.dart
index e22f636e..954f14b1 100644
--- a/lib/utils/core/i_core_utils.dart
+++ b/lib/utils/core/i_core_utils.dart
@@ -2,6 +2,14 @@ abstract class ICoreUtils {
/// Returns true if the given [url] is a valid HTTP or HTTPS URL.
bool isValidProjectID(String projectId);
+ Future isRestrictedRegion();
+
+ Future getApiUrl();
+
+ Future getBlockchainApiUrl();
+
+ Future getAnalyticsUrl();
+
/// Returns true if the given [url] is a valid HTTP or HTTPS URL.
bool isHttpUrl(String url);
diff --git a/lib/utils/w3m_logger.dart b/lib/utils/w3m_logger.dart
index 9aeea981..f7f0aee3 100644
--- a/lib/utils/w3m_logger.dart
+++ b/lib/utils/w3m_logger.dart
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';
+// @Deprecated('W3MLoggerUtil is going to be deprecated soon. Don\'t use it')
class W3MLoggerUtil {
static Logger logger = Logger(
level: Level.off,
diff --git a/lib/version.dart b/lib/version.dart
index 50e65e2a..cc991587 100644
--- a/lib/version.dart
+++ b/lib/version.dart
@@ -1,2 +1,2 @@
// Generated code. Do not modify.
-const packageVersion = '3.1.2';
+const packageVersion = '3.1.3-beta01';
diff --git a/lib/web3modal_flutter.dart b/lib/web3modal_flutter.dart
index 515783e2..79b9d0db 100644
--- a/lib/web3modal_flutter.dart
+++ b/lib/web3modal_flutter.dart
@@ -10,7 +10,6 @@ export 'models/w3m_wallet_info.dart';
/// Utils
export 'utils/w3m_chains_presets.dart';
-export 'utils/w3m_logger.dart';
/// Theme
export 'theme/w3m_theme.dart';
diff --git a/lib/widgets/miscellaneous/all_wallets_header.dart b/lib/widgets/miscellaneous/all_wallets_header.dart
index cd2a98bf..c42cd46f 100644
--- a/lib/widgets/miscellaneous/all_wallets_header.dart
+++ b/lib/widgets/miscellaneous/all_wallets_header.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:web3modal_flutter/pages/qr_code_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/services/explorer_service/explorer_service_singleton.dart';
import 'package:web3modal_flutter/theme/constants.dart';
import 'package:web3modal_flutter/widgets/icons/themed_icon.dart';
@@ -35,7 +36,13 @@ class AllWalletsHeader extends StatelessWidget {
size: kSearchFieldHeight,
iconPath: 'assets/icons/code.svg',
onPressed: () {
- widgetStack.instance.push(const QRCodePage());
+ widgetStack.instance.push(
+ const QRCodePage(),
+ event: SelectWalletEvent(
+ name: 'WalletConnect',
+ platform: AnalyticsPlatform.qrcode,
+ ),
+ );
},
),
const SizedBox.square(dimension: 2.0),
diff --git a/lib/widgets/w3m_qr_code.dart b/lib/widgets/qr_code_view.dart
similarity index 95%
rename from lib/widgets/w3m_qr_code.dart
rename to lib/widgets/qr_code_view.dart
index af5c2658..21a5a170 100644
--- a/lib/widgets/w3m_qr_code.dart
+++ b/lib/widgets/qr_code_view.dart
@@ -8,8 +8,9 @@ import 'package:web3modal_flutter/web3modal_flutter.dart';
import 'package:web3modal_flutter/widgets/miscellaneous/content_loading.dart';
import 'package:web3modal_flutter/widgets/miscellaneous/responsive_container.dart';
-class QRCodeWidget extends StatelessWidget {
- const QRCodeWidget({
+// TODO This file should be called qr_code_view.dart
+class QRCodeView extends StatelessWidget {
+ const QRCodeView({
super.key,
required this.uri,
this.logoPath = '',
diff --git a/lib/widgets/w3m_network_select_button.dart b/lib/widgets/w3m_network_select_button.dart
index 25384416..a77d048b 100644
--- a/lib/widgets/w3m_network_select_button.dart
+++ b/lib/widgets/w3m_network_select_button.dart
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:web3modal_flutter/models/w3m_chain_info.dart';
import 'package:web3modal_flutter/pages/select_network_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/analytics_service_singleton.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/services/w3m_service/i_w3m_service.dart';
import 'package:web3modal_flutter/widgets/widget_stack/widget_stack_singleton.dart';
import 'package:web3modal_flutter/widgets/buttons/base_button.dart';
@@ -33,8 +35,8 @@ class _W3MNetworkSelectButtonState extends State {
@override
void dispose() {
- super.dispose();
widget.service.removeListener(_onServiceUpdate);
+ super.dispose();
}
@override
@@ -48,6 +50,7 @@ class _W3MNetworkSelectButtonState extends State {
}
void _onConnectPressed(BuildContext context) {
+ analyticsService.instance.sendEvent(ClickNetworksEvent());
widget.service.openModal(
context,
SelectNetworkPage(
diff --git a/lib/widgets/widget_stack/i_widget_stack.dart b/lib/widgets/widget_stack/i_widget_stack.dart
index 641a0603..d3980361 100644
--- a/lib/widgets/widget_stack/i_widget_stack.dart
+++ b/lib/widgets/widget_stack/i_widget_stack.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
abstract class IWidgetStack with ChangeNotifier {
abstract final ValueNotifier onRenderScreen;
@@ -7,7 +8,11 @@ abstract class IWidgetStack with ChangeNotifier {
Widget getCurrent();
/// Pushes a widget to the stack.
- void push(Widget widget, {bool renderScreen = false});
+ void push(
+ Widget widget, {
+ bool renderScreen = false,
+ AnalyticsEvent? event,
+ });
/// Removes a widget from the stack.
void pop();
diff --git a/lib/widgets/widget_stack/widget_stack.dart b/lib/widgets/widget_stack/widget_stack.dart
index 6bd7d21e..8adcd3c8 100644
--- a/lib/widgets/widget_stack/widget_stack.dart
+++ b/lib/widgets/widget_stack/widget_stack.dart
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:web3modal_flutter/pages/wallets_list_short_page.dart';
+import 'package:web3modal_flutter/services/analytics_service/analytics_service_singleton.dart';
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart';
import 'package:web3modal_flutter/widgets/widget_stack/i_widget_stack.dart';
//
import 'package:web3modal_flutter/utils/platform/i_platform_utils.dart';
@@ -16,7 +18,14 @@ class WidgetStack extends IWidgetStack {
Widget getCurrent() => _stack.last;
@override
- void push(Widget widget, {bool renderScreen = false}) {
+ void push(
+ Widget widget, {
+ bool renderScreen = false,
+ AnalyticsEvent? event,
+ }) {
+ if (event != null) {
+ analyticsService.instance.sendEvent(event);
+ }
onRenderScreen.value = renderScreen;
_stack.add(widget);
notifyListeners();
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 3ebb6b9e..03439581 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import flutter_timezone
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
@@ -12,6 +13,7 @@ import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index 9544dc9f..3c3cf8a6 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -355,6 +355,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_timezone:
+ dependency: "direct main"
+ description:
+ name: flutter_timezone
+ sha256: "06b35132c98fa188db3c4b654b7e1af7ccd01dfe12a004d58be423357605fb24"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -364,10 +372,10 @@ packages:
dependency: "direct dev"
description:
name: freezed
- sha256: "21bf2825311de65501d22e563e3d7605dff57fb5e6da982db785ae5372ff018a"
+ sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5"
url: "https://pub.dev"
source: hosted
- version: "2.4.5"
+ version: "2.4.7"
freezed_annotation:
dependency: "direct main"
description:
@@ -998,13 +1006,13 @@ packages:
source: hosted
version: "3.1.0"
uuid:
- dependency: transitive
+ dependency: "direct main"
description:
name: uuid
- sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921
+ sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
- version: "4.2.1"
+ version: "4.3.3"
vector_graphics:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index b4aba012..8235154d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: web3modal_flutter
description: "WalletConnect Web3Modal: Simple, intuitive wallet login. With this drop-in UI SDK, enable any wallet's users to seamlessly log in to your app and enjoy a unified experience"
-version: 3.1.2
+version: 3.1.3-beta01
repository: https://github.com/WalletConnect/Web3ModalFlutter
environment:
@@ -16,12 +16,14 @@ dependencies:
flutter:
sdk: flutter
flutter_svg: ^2.0.9
- freezed_annotation: ^2.2.0
+ flutter_timezone: ^1.0.8
+ freezed_annotation: ^2.4.1
http: ^1.1.2
json_annotation: ^4.8.1
qr_flutter_wc: ^0.0.3
shimmer: ^3.0.0
url_launcher: ^6.2.3
+ uuid: ^4.3.3
walletconnect_flutter_v2: ^2.2.0
dev_dependencies:
@@ -29,7 +31,7 @@ dev_dependencies:
build_version: ^2.1.1
flutter_test:
sdk: flutter
- freezed: ^2.4.5
+ freezed: ^2.4.7
json_serializable: ^6.7.0
lints: ^3.0.0
mockito: ^5.4.3
diff --git a/test/mock_classes.mocks.dart b/test/mock_classes.mocks.dart
index d7adce1f..ddbeb87e 100644
--- a/test/mock_classes.mocks.dart
+++ b/test/mock_classes.mocks.dart
@@ -27,6 +27,8 @@ import 'package:walletconnect_flutter_v2/apis/core/store/i_generic_store.dart'
import 'package:walletconnect_flutter_v2/apis/core/store/i_store.dart' as _i6;
import 'package:walletconnect_flutter_v2/apis/sign_api/i_sessions.dart' as _i5;
import 'package:web3modal_flutter/models/grid_item.dart' as _i30;
+import 'package:web3modal_flutter/services/analytics_service/models/analytics_event.dart'
+ as _i35;
import 'package:web3modal_flutter/services/blockchain_api_service/blockchain_api_utils.dart'
as _i31;
import 'package:web3modal_flutter/services/blockchain_api_service/blockchain_identity.dart'
@@ -660,12 +662,16 @@ class MockW3MService extends _i1.Mock implements _i3.W3MService {
_i14.Future selectChain(
_i3.W3MChainInfo? chainInfo, {
bool? switchChain = false,
+ bool? logEvent = true,
}) =>
(super.noSuchMethod(
Invocation.method(
#selectChain,
[chainInfo],
- {#switchChain: switchChain},
+ {
+ #switchChain: switchChain,
+ #logEvent: logEvent,
+ },
),
returnValue: _i14.Future.value(),
returnValueForMissingStub: _i14.Future.value(),
@@ -2338,14 +2344,6 @@ class MockBlockchainApiUtils extends _i1.Mock
_i1.throwOnMissingStub(this);
}
- @override
- String get blockchainApiUriRoot => (super.noSuchMethod(
- Invocation.getter(#blockchainApiUriRoot),
- returnValue: _i13.dummyValue(
- this,
- Invocation.getter(#blockchainApiUriRoot),
- ),
- ) as String);
@override
String get projectId => (super.noSuchMethod(
Invocation.getter(#projectId),
@@ -2554,12 +2552,16 @@ class MockWidgetStack extends _i1.Mock implements _i34.WidgetStack {
void push(
_i11.Widget? widget, {
bool? renderScreen = false,
+ _i35.AnalyticsEvent? event,
}) =>
super.noSuchMethod(
Invocation.method(
#push,
[widget],
- {#renderScreen: renderScreen},
+ {
+ #renderScreen: renderScreen,
+ #event: event,
+ },
),
returnValueForMissingStub: null,
);