diff --git a/packages/reown_appkit/CHANGELOG.md b/packages/reown_appkit/CHANGELOG.md index 2b6d35a4..00743554 100644 --- a/packages/reown_appkit/CHANGELOG.md +++ b/packages/reown_appkit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0-beta01 + +- Social Logins + ## 1.0.1 - Updated Coinbase Wallet SDK to support Android Gradle Plugin 8 diff --git a/packages/reown_appkit/analysis_options.yaml b/packages/reown_appkit/analysis_options.yaml index b677fc6a..3e16fe1d 100644 --- a/packages/reown_appkit/analysis_options.yaml +++ b/packages/reown_appkit/analysis_options.yaml @@ -29,6 +29,7 @@ linter: sort_pub_dependencies: true avoid_unnecessary_containers: true cancel_subscriptions: true + public_member_api_docs: false analyzer: exclude: diff --git a/packages/reown_appkit/dart_dependency_validator.yaml b/packages/reown_appkit/dart_dependency_validator.yaml new file mode 100644 index 00000000..46c10a3d --- /dev/null +++ b/packages/reown_appkit/dart_dependency_validator.yaml @@ -0,0 +1,10 @@ +# dart_dependency_validator.yaml + +# Set true if you allow pinned packages in your project. +# allow_pins: true +# Exclude one or more paths from being scanned. Supports glob syntax. +exclude: + - 'example/**' # Glob's are supported +# Ignore one or more packages. +# ignore: +# - analyzer \ No newline at end of file diff --git a/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml b/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml index fe850f89..a464976e 100644 --- a/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml +++ b/packages/reown_appkit/example/base/android/app/src/main/AndroidManifest.xml @@ -52,8 +52,8 @@ - - + + - - - - - - - - - - - - diff --git a/packages/reown_appkit/example/base/lib/main.dart b/packages/reown_appkit/example/base/lib/main.dart index c22e52a5..1a4c7f2f 100644 --- a/packages/reown_appkit/example/base/lib/main.dart +++ b/packages/reown_appkit/example/base/lib/main.dart @@ -124,11 +124,40 @@ class _MyHomePageState extends State { _appKit!.onSessionConnect.subscribe(_onSessionConnect); _appKit!.onSessionAuthResponse.subscribe(_onSessionAuthResponse); + // See https://docs.reown.com/appkit/flutter/core/custom-chains + final testNetworks = ReownAppKitModalNetworks.test['eip155'] ?? []; + ReownAppKitModalNetworks.addNetworks('eip155', testNetworks); + _appKitModal = ReownAppKitModal( context: context, appKit: _appKit, siweConfig: _siweConfig(), - enableEmail: true, + enableAnalytics: true, + featuresConfig: FeaturesConfig( + email: true, + socials: [ + AppKitSocialOption.Farcaster, + AppKitSocialOption.X, + AppKitSocialOption.Apple, + AppKitSocialOption.Discord, + ], + showMainWallets: false, + ), + // requiredNamespaces: {}, + // optionalNamespaces: {}, + // includedWalletIds: {}, + featuredWalletIds: { + 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', // Coinbase + '18450873727504ae9315a084fa7624b5297d2fe5880f0982979c17345a138277', // Kraken Wallet + 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // Metamask + '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', // Rainbow + 'c03dfee351b6fcc421b4494ea33b9d4b92a984f87aa76d1663bb28705e95034a', // Uniswap + '38f5d18bd8522c244bdd70cb4a68e0e718865155811c043f052fb9f1c51de662', // Bitget + }, + // excludedWalletIds: { + // 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', // Coinbase + // }, + // MORE WALLETS https://explorer.walletconnect.com/?type=wallet&chains=eip155%3A1 ); _appKitModal!.onModalConnect.subscribe(_onModalConnect); @@ -162,12 +191,12 @@ class _MyHomePageState extends State { icon: Icons.home, ), PageData( - page: PairingsPage(appKit: _appKit!), + page: PairingsPage(appKitModal: _appKitModal!), title: StringConstants.pairingsPageTitle, icon: Icons.vertical_align_center_rounded, ), PageData( - page: SessionsPage(appKit: _appKit!), + page: SessionsPage(appKitModal: _appKitModal!), title: StringConstants.sessionsPageTitle, icon: Icons.workspaces_filled, ), @@ -249,8 +278,9 @@ class _MyHomePageState extends State { super.dispose(); } - void _logListener(LogEvent event) { - if (event.level == Level.debug) { + void _logListener(event) { + if ('${event.level}' == 'Level.debug' || + '${event.level}' == 'Level.error') { // TODO send to mixpanel log('${event.message}'); } else { @@ -462,11 +492,12 @@ class _MyHomePageState extends State { ); void _onModalConnect(ModalConnect? event) async { - setState(() {}); debugPrint('[ExampleApp] _onModalConnect ${event?.session.toJson()}'); + setState(() {}); } void _onModalUpdate(ModalConnect? event) { + debugPrint('[ExampleApp] _onModalUpdate ${event?.session.toJson()}'); setState(() {}); } diff --git a/packages/reown_appkit/example/base/lib/pages/connect_page.dart b/packages/reown_appkit/example/base/lib/pages/connect_page.dart index c7dff84c..70903424 100644 --- a/packages/reown_appkit/example/base/lib/pages/connect_page.dart +++ b/packages/reown_appkit/example/base/lib/pages/connect_page.dart @@ -16,6 +16,7 @@ import 'package:reown_appkit_dapp/utils/crypto/solana.dart'; import 'package:reown_appkit_dapp/utils/sample_wallets.dart'; import 'package:reown_appkit_dapp/utils/string_constants.dart'; import 'package:reown_appkit_dapp/widgets/chain_button.dart'; +import 'package:reown_appkit_dapp/widgets/method_dialog.dart'; class ConnectPage extends StatefulWidget { const ConnectPage({ @@ -36,6 +37,12 @@ class ConnectPageState extends State { @override void initState() { super.initState(); + widget.appKitModal.onModalConnect.subscribe(_onModalConnect); + widget.appKitModal.onModalUpdate.subscribe(_onModalUpdate); + widget.appKitModal.onModalNetworkChange.subscribe(_onModalNetworkChange); + widget.appKitModal.onModalDisconnect.subscribe(_onModalDisconnect); + widget.appKitModal.onModalError.subscribe(_onModalError); + // widget.appKitModal.appKit!.onSessionConnect.subscribe( _onSessionConnect, ); @@ -49,6 +56,11 @@ class ConnectPageState extends State { @override void dispose() { + widget.appKitModal.onModalConnect.unsubscribe(_onModalConnect); + widget.appKitModal.onModalUpdate.unsubscribe(_onModalUpdate); + widget.appKitModal.onModalNetworkChange.unsubscribe(_onModalNetworkChange); + widget.appKitModal.onModalDisconnect.unsubscribe(_onModalDisconnect); + widget.appKitModal.onModalError.unsubscribe(_onModalError); widget.appKitModal.onModalDisconnect.unsubscribe( _onModalDisconnect, ); @@ -192,8 +204,14 @@ class ConnectPageState extends State { const SizedBox(height: StyleConstants.linear8), Visibility( visible: widget.appKitModal.isConnected, - child: AppKitModalAccountButton( - appKit: widget.appKitModal, + child: Column( + children: [ + AppKitModalAccountButton( + appKit: widget.appKitModal, + ), + const SizedBox.square(dimension: 8.0), + ...(_buildRequestButtons()), + ], ), ), const SizedBox(height: StyleConstants.linear8), @@ -408,6 +426,38 @@ class ConnectPageState extends State { ); } + List _buildRequestButtons() { + return widget.appKitModal.getApprovedMethods()?.map((method) { + final topic = widget.appKitModal.session!.topic ?? ''; + final chainId = widget.appKitModal.selectedChain!.chainId; + final address = widget.appKitModal.session!.address!; + final requestParams = EIP155.getParams(method, address); + final enabled = requestParams != null; + return Container( + height: 40.0, + width: double.infinity, + margin: const EdgeInsets.symmetric( + vertical: StyleConstants.linear8, + ), + child: ElevatedButton( + onPressed: enabled + ? () { + widget.appKitModal.launchConnectedWallet(); + final future = widget.appKitModal.request( + topic: topic, + chainId: chainId, + request: requestParams, + ); + MethodDialog.show(context, method, future); + } + : null, + child: Text(method), + ), + ); + }).toList() ?? + []; + } + Future _onConnect({ required String nativeLink, VoidCallback? closeModal, @@ -559,10 +609,26 @@ class ConnectPageState extends State { } } + void _onModalConnect(ModalConnect? event) async { + setState(() {}); + } + + void _onModalUpdate(ModalConnect? event) { + setState(() {}); + } + + void _onModalNetworkChange(ModalNetworkChange? event) { + setState(() {}); + } + void _onModalDisconnect(ModalDisconnect? event) { setState(() {}); } + void _onModalError(ModalError? event) { + setState(() {}); + } + ButtonStyle get _buttonStyle => ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith( (states) { diff --git a/packages/reown_appkit/example/base/lib/pages/pairings_page.dart b/packages/reown_appkit/example/base/lib/pages/pairings_page.dart index a3db3e02..46f05178 100644 --- a/packages/reown_appkit/example/base/lib/pages/pairings_page.dart +++ b/packages/reown_appkit/example/base/lib/pages/pairings_page.dart @@ -8,10 +8,10 @@ import 'package:reown_appkit_dapp/widgets/pairing_item.dart'; class PairingsPage extends StatefulWidget { const PairingsPage({ super.key, - required this.appKit, + required this.appKitModal, }); - final ReownAppKit appKit; + final ReownAppKitModal appKitModal; @override PairingsPageState createState() => PairingsPageState(); @@ -19,26 +19,31 @@ class PairingsPage extends StatefulWidget { class PairingsPageState extends State { List _pairings = []; + late IReownAppKit _appKit; @override void initState() { - _pairings = widget.appKit.pairings.getAll(); - // widget.appKit.onSessionDelete.subscribe(_onSessionDelete); - widget.appKit.core.pairing.onPairingDelete.subscribe(_onPairingDelete); - widget.appKit.core.pairing.onPairingExpire.subscribe(_onPairingDelete); + _appKit = widget.appKitModal.appKit!; + _pairings = _appKit.pairings.getAll(); + _appKit.core.pairing.onPairingDelete.subscribe(_onPairingDelete); + _appKit.core.pairing.onPairingExpire.subscribe(_onPairingDelete); super.initState(); } @override void dispose() { - // widget.appKit.onSessionDelete.unsubscribe(_onSessionDelete); - widget.appKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); - widget.appKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); + _appKit.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); + _appKit.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); super.dispose(); } @override Widget build(BuildContext context) { + if (_pairings.isEmpty) { + return Center( + child: Text('No relay pairings'), + ); + } final List pairingItems = _pairings .map( (PairingInfo pairing) => PairingItem( @@ -71,7 +76,7 @@ class PairingsPageState extends State { ), onPressed: () async { try { - widget.appKit.core.pairing.disconnect( + _appKit.core.pairing.disconnect( topic: pairing.topic, ); Navigator.of(context).pop(); @@ -104,7 +109,7 @@ class PairingsPageState extends State { void _onPairingDelete(PairingEvent? event) { setState(() { - _pairings = widget.appKit.pairings.getAll(); + _pairings = _appKit.pairings.getAll(); }); } } diff --git a/packages/reown_appkit/example/base/lib/pages/sessions_page.dart b/packages/reown_appkit/example/base/lib/pages/sessions_page.dart index 3b23b9d7..412e318e 100644 --- a/packages/reown_appkit/example/base/lib/pages/sessions_page.dart +++ b/packages/reown_appkit/example/base/lib/pages/sessions_page.dart @@ -9,37 +9,44 @@ import 'package:reown_appkit_dapp/widgets/session_widget.dart'; class SessionsPage extends StatefulWidget { const SessionsPage({ super.key, - required this.appKit, + required this.appKitModal, }); - final ReownAppKit appKit; + final ReownAppKitModal appKitModal; @override SessionsPageState createState() => SessionsPageState(); } class SessionsPageState extends State { + late IReownAppKit _appKit; Map _activeSessions = {}; - String _selectedSession = ''; + String _selectedTopic = ''; @override void initState() { - _activeSessions = widget.appKit.getActiveSessions(); - widget.appKit.onSessionDelete.subscribe(_onSessionDelete); - widget.appKit.onSessionExpire.subscribe(_onSessionExpire); + _appKit = widget.appKitModal.appKit!; + _activeSessions = _appKit.getActiveSessions(); + _appKit.onSessionDelete.subscribe(_onSessionDelete); + _appKit.onSessionExpire.subscribe(_onSessionExpire); super.initState(); } @override void dispose() { - widget.appKit.onSessionDelete.unsubscribe(_onSessionDelete); - widget.appKit.onSessionExpire.unsubscribe(_onSessionExpire); + _appKit.onSessionDelete.unsubscribe(_onSessionDelete); + _appKit.onSessionExpire.unsubscribe(_onSessionExpire); super.dispose(); } @override Widget build(BuildContext context) { final List sessions = _activeSessions.values.toList(); + if (sessions.isEmpty) { + return Center( + child: Text('No relay sessions'), + ); + } return Center( child: Container( constraints: const BoxConstraints( @@ -53,14 +60,14 @@ class SessionsPageState extends State { materialGapSize: 0.0, expansionCallback: (int index, bool isExpanded) { setState(() { - _selectedSession = !isExpanded ? '' : sessions[index].topic; + _selectedTopic = !isExpanded ? '' : sessions[index].topic; }); }, children: sessions .map( (session) => ExpansionPanel( canTapOnHeader: true, - isExpanded: _selectedSession == session.topic, + isExpanded: _selectedTopic == session.topic, backgroundColor: Colors.blue.withOpacity(0.2), headerBuilder: (context, isExpanded) { return SessionItem( @@ -84,7 +91,7 @@ class SessionsPageState extends State { } Widget _buildSessionView() { - if (_selectedSession == '') { + if (_selectedTopic == '') { return const Center( child: Text( StringConstants.noSessionSelected, @@ -93,29 +100,27 @@ class SessionsPageState extends State { ); } - final SessionData session = _activeSessions[_selectedSession]!; - return SessionWidget( - appKit: widget.appKit, - session: session, + appKitModal: widget.appKitModal, + sessionTopic: _selectedTopic, ); } void _onSessionDelete(SessionDelete? event) { setState(() { - if (event!.topic == _selectedSession) { - _selectedSession = ''; + if (event!.topic == _selectedTopic) { + _selectedTopic = ''; } - _activeSessions = widget.appKit.getActiveSessions(); + _activeSessions = _appKit.getActiveSessions(); }); } void _onSessionExpire(SessionExpire? event) { setState(() { - if (event!.topic == _selectedSession) { - _selectedSession = ''; + if (event!.topic == _selectedTopic) { + _selectedTopic = ''; } - _activeSessions = widget.appKit.getActiveSessions(); + _activeSessions = _appKit.getActiveSessions(); }); } } diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart b/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart index 0556792c..5cfde85a 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/eip155.dart @@ -36,7 +36,7 @@ class EIP155 { }; static Future callMethod({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String method, required ChainMetadata chainData, @@ -49,7 +49,6 @@ class EIP155 { topic: topic, chainId: chainData.chainId, address: address, - message: testSignData, ); case 'eth_sign': return ethSign( @@ -57,7 +56,6 @@ class EIP155 { topic: topic, chainId: chainData.chainId, address: address, - message: testSignData, ); case 'eth_signTypedData': return ethSignTypedData( @@ -65,33 +63,18 @@ class EIP155 { topic: topic, chainId: chainData.chainId, address: address, - data: typedData, ); case 'eth_signTransaction': return ethSignTransaction( appKit: appKit, topic: topic, chainId: chainData.chainId, - transaction: Transaction( - from: EthereumAddress.fromHex(address), - to: EthereumAddress.fromHex( - '0x59e2f66C0E96803206B6486cDb39029abAE834c0', - ), - value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012 - ), ); case 'eth_sendTransaction': return ethSendTransaction( appKit: appKit, topic: topic, chainId: chainData.chainId, - transaction: Transaction( - from: EthereumAddress.fromHex(address), - to: EthereumAddress.fromHex( - '0x59e2f66C0E96803206B6486cDb39029abAE834c0', - ), - value: EtherAmount.fromInt(EtherUnit.finney, 11), // == 0.011 - ), ); default: throw 'Method unimplemented'; @@ -99,7 +82,7 @@ class EIP155 { } static Future callSmartContract({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String address, required String action, @@ -148,93 +131,70 @@ class EIP155 { } static Future personalSign({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String chainId, required String address, - required String message, }) async { - final bytes = utf8.encode(message); - final encoded = bytesToHex(bytes); - return await appKit.request( topic: topic, chainId: chainId, - request: SessionRequestParams( - method: methods[EIP155Methods.personalSign]!, - params: [encoded, address], - ), + request: getParams('personal_sign', address)!, ); } static Future ethSign({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String chainId, required String address, - required String message, }) async { return await appKit.request( topic: topic, chainId: chainId, - request: SessionRequestParams( - method: methods[EIP155Methods.ethSign]!, - params: [address, message], - ), + request: getParams('eth_sign', address)!, ); } static Future ethSignTypedData({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String chainId, required String address, - required String data, }) async { return await appKit.request( topic: topic, chainId: chainId, - request: SessionRequestParams( - method: methods[EIP155Methods.ethSignTypedData]!, - params: [address, data], - ), + request: getParams('eth_signTypedData', address)!, ); } static Future ethSignTransaction({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String chainId, - required Transaction transaction, }) async { return await appKit.request( topic: topic, chainId: chainId, - request: SessionRequestParams( - method: methods[EIP155Methods.ethSignTransaction]!, - params: [transaction.toJson()], - ), + request: getParams('eth_signTransaction', '')!, ); } static Future ethSendTransaction({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String chainId, - required Transaction transaction, }) async { return await appKit.request( topic: topic, chainId: chainId, - request: SessionRequestParams( - method: methods[EIP155Methods.ethSendTransaction]!, - params: [transaction.toJson()], - ), + request: getParams('eth_sendTransaction', '')!, ); } static Future readSmartContract({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String rpcUrl, required String address, required DeployedContract contract, @@ -274,4 +234,54 @@ class EIP155 { 'balance': oCcy.format(balance), }; } + + static SessionRequestParams? getParams(String method, String address) { + switch (method) { + case 'personal_sign': + final bytes = utf8.encode(testSignData); + final encoded = bytesToHex(bytes, include0x: true); + return SessionRequestParams( + method: methods[EIP155Methods.personalSign]!, + params: [encoded, address], + ); + case 'eth_sign': + return SessionRequestParams( + method: methods[EIP155Methods.ethSign]!, + params: [address, testSignData], + ); + case 'eth_signTypedData': + return SessionRequestParams( + method: methods[EIP155Methods.ethSignTypedData]!, + params: [address, typedData], + ); + case 'eth_signTransaction': + return SessionRequestParams( + method: methods[EIP155Methods.ethSignTransaction]!, + params: [ + Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012 + ).toJson(), + ], + ); + case 'eth_sendTransaction': + return SessionRequestParams( + method: methods[EIP155Methods.ethSendTransaction]!, + params: [ + Transaction( + from: EthereumAddress.fromHex(address), + to: EthereumAddress.fromHex( + '0x59e2f66C0E96803206B6486cDb39029abAE834c0', + ), + value: EtherAmount.fromInt(EtherUnit.finney, 12), // == 0.012 + ).toJson(), + ], + ); + default: + return null; + } + } } diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart b/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart index 27eec4f9..02d770bc 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/polkadot.dart @@ -1,4 +1,5 @@ import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit_dapp/models/chain_metadata.dart'; enum PolkadotMethods { polkadotSignTransaction, @@ -18,17 +19,17 @@ class Polkadot { static final Map events = {}; static Future callMethod({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String method, - required String chainId, + required ChainMetadata chainData, required String address, }) { switch (method) { case 'polkadot_signMessage': return appKit.request( topic: topic, - chainId: chainId, + chainId: chainData.chainId, request: SessionRequestParams( method: method, params: { @@ -41,7 +42,7 @@ class Polkadot { case 'polkadot_signTransaction': return appKit.request( topic: topic, - chainId: chainId, + chainId: chainData.chainId, request: SessionRequestParams( method: method, params: { diff --git a/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart b/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart index 329d83ce..f060d5b3 100644 --- a/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart +++ b/packages/reown_appkit/example/base/lib/utils/crypto/solana.dart @@ -1,9 +1,7 @@ import 'dart:convert'; import 'package:bs58/bs58.dart'; import 'package:solana_web3/solana_web3.dart' as solana; - import 'package:reown_appkit/reown_appkit.dart'; - import 'package:reown_appkit_dapp/models/chain_metadata.dart'; enum SolanaMethods { @@ -24,12 +22,11 @@ class Solana { static final Map events = {}; static Future callMethod({ - required ReownAppKit appKit, + required IReownAppKit appKit, required String topic, required String method, required ChainMetadata chainData, required String address, - bool isV0 = false, }) async { switch (method) { case 'solana_signMessage': diff --git a/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart b/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart index 7e225155..ccf15e91 100644 --- a/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart +++ b/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart @@ -20,7 +20,7 @@ class DeepLinkHandler { onError: _onError, ); } catch (e) { - debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + debugPrint('[SampleDapp] checkInitialLink $e'); } } @@ -34,7 +34,7 @@ class DeepLinkHandler { try { _methodChannel.invokeMethod('initialLink'); } catch (e) { - debugPrint('[SampleWallet] [DeepLinkHandler] checkInitialLink $e'); + debugPrint('[SampleDapp] checkInitialLink $e'); } } diff --git a/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart b/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart index 885e8e5d..24b43c33 100644 --- a/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart +++ b/packages/reown_appkit/example/base/lib/utils/sample_wallets.dart @@ -32,9 +32,8 @@ class WCSampleWallets { 'platform': ['android'], 'id': '123456789012345678901234567894', 'schema': 'kotlin-web3wallet://wc', - 'bundleId': 'com.walletconnect.sample.wallet.internal', - 'universal': - 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_internal', + 'bundleId': 'com.reown.sample.wallet.internal', + 'universal': 'https://appkit-lab.reown.com/wallet_internal', }, ]; @@ -68,9 +67,8 @@ class WCSampleWallets { 'platform': ['android'], 'id': '123456789012345678901234567893', 'schema': 'kotlin-web3wallet://wc', - 'bundleId': 'com.walletconnect.sample.wallet', - 'universal': - 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_release', + 'bundleId': 'com.reown.sample.wallet', + 'universal': 'https://appkit-lab.reown.com/wallet_release', }, ]; diff --git a/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart b/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart index 9b20505e..594d8a19 100644 --- a/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart +++ b/packages/reown_appkit/example/base/lib/utils/smart_contracts.dart @@ -277,3 +277,783 @@ class SepoliaTestContract { } ]; } + +class AAVESepoliaContract { + // AAVE on Sepolia + // https://sepolia.etherscan.io/token/0x88541670E55cC00bEEFD87eB59EDd1b7C511AC9a + static const contractAddress = '0x88541670E55cC00bEEFD87eB59EDd1b7C511AC9a'; + + static const contractABI = [ + { + 'inputs': [ + {'internalType': 'string', 'name': 'name', 'type': 'string'}, + {'internalType': 'string', 'name': 'symbol', 'type': 'string'}, + {'internalType': 'uint8', 'name': 'decimals', 'type': 'uint8'}, + {'internalType': 'address', 'name': 'owner', 'type': 'address'} + ], + 'stateMutability': 'nonpayable', + 'type': 'constructor' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'owner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'spender', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'previousOwner', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'newOwner', + 'type': 'address' + } + ], + 'name': 'OwnershipTransferred', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'internalType': 'address', + 'name': 'from', + 'type': 'address' + }, + { + 'indexed': true, + 'internalType': 'address', + 'name': 'to', + 'type': 'address' + }, + { + 'indexed': false, + 'internalType': 'uint256', + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Transfer', + 'type': 'event' + }, + { + 'inputs': [], + 'name': 'DOMAIN_SEPARATOR', + 'outputs': [ + {'internalType': 'bytes32', 'name': '', 'type': 'bytes32'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'EIP712_REVISION', + 'outputs': [ + {'internalType': 'bytes', 'name': '', 'type': 'bytes'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'PERMIT_TYPEHASH', + 'outputs': [ + {'internalType': 'bytes32', 'name': '', 'type': 'bytes32'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'}, + {'internalType': 'address', 'name': 'spender', 'type': 'address'} + ], + 'name': 'allowance', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'} + ], + 'name': 'balanceOf', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + {'internalType': 'uint8', 'name': '', 'type': 'uint8'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + { + 'internalType': 'uint256', + 'name': 'subtractedValue', + 'type': 'uint256' + } + ], + 'name': 'decreaseAllowance', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'addedValue', 'type': 'uint256'} + ], + 'name': 'increaseAllowance', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'account', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'mint', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'} + ], + 'name': 'mint', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'name', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'} + ], + 'name': 'nonces', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'owner', + 'outputs': [ + {'internalType': 'address', 'name': '', 'type': 'address'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'owner', 'type': 'address'}, + {'internalType': 'address', 'name': 'spender', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'value', 'type': 'uint256'}, + {'internalType': 'uint256', 'name': 'deadline', 'type': 'uint256'}, + {'internalType': 'uint8', 'name': 'v', 'type': 'uint8'}, + {'internalType': 'bytes32', 'name': 'r', 'type': 'bytes32'}, + {'internalType': 'bytes32', 'name': 's', 'type': 'bytes32'} + ], + 'name': 'permit', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'renounceOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'symbol', + 'outputs': [ + {'internalType': 'string', 'name': '', 'type': 'string'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + {'internalType': 'uint256', 'name': '', 'type': 'uint256'} + ], + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'recipient', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'transfer', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'sender', 'type': 'address'}, + {'internalType': 'address', 'name': 'recipient', 'type': 'address'}, + {'internalType': 'uint256', 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'transferFrom', + 'outputs': [ + {'internalType': 'bool', 'name': '', 'type': 'bool'} + ], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'internalType': 'address', 'name': 'newOwner', 'type': 'address'} + ], + 'name': 'transferOwnership', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + } + ]; +} + +class USDTContract { + // USDT-ERC20 + // https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7 + static const contractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; + + static const contractABI = [ + { + 'constant': true, + 'inputs': [], + 'name': 'name', + 'outputs': [ + {'name': '', 'type': 'string'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_upgradedAddress', 'type': 'address'} + ], + 'name': 'deprecate', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_spender', 'type': 'address'}, + {'name': '_value', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'deprecated', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_evilUser', 'type': 'address'} + ], + 'name': 'addBlackList', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_from', 'type': 'address'}, + {'name': '_to', 'type': 'address'}, + {'name': '_value', 'type': 'uint256'} + ], + 'name': 'transferFrom', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'upgradedAddress', + 'outputs': [ + {'name': '', 'type': 'address'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '', 'type': 'address'} + ], + 'name': 'balances', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'maximumFee', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': '_totalSupply', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [], + 'name': 'unpause', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '_maker', 'type': 'address'} + ], + 'name': 'getBlackListStatus', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '', 'type': 'address'}, + {'name': '', 'type': 'address'} + ], + 'name': 'allowed', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'paused', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': 'who', 'type': 'address'} + ], + 'name': 'balanceOf', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [], + 'name': 'pause', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'getOwner', + 'outputs': [ + {'name': '', 'type': 'address'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'owner', + 'outputs': [ + {'name': '', 'type': 'address'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'symbol', + 'outputs': [ + {'name': '', 'type': 'string'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_to', 'type': 'address'}, + {'name': '_value', 'type': 'uint256'} + ], + 'name': 'transfer', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'newBasisPoints', 'type': 'uint256'}, + {'name': 'newMaxFee', 'type': 'uint256'} + ], + 'name': 'setParams', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'amount', 'type': 'uint256'} + ], + 'name': 'issue', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'amount', 'type': 'uint256'} + ], + 'name': 'redeem', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '_owner', 'type': 'address'}, + {'name': '_spender', 'type': 'address'} + ], + 'name': 'allowance', + 'outputs': [ + {'name': 'remaining', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'basisPointsRate', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + {'name': '', 'type': 'address'} + ], + 'name': 'isBlackListed', + 'outputs': [ + {'name': '', 'type': 'bool'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_clearedUser', 'type': 'address'} + ], + 'name': 'removeBlackList', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'MAX_UINT', + 'outputs': [ + {'name': '', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': 'newOwner', 'type': 'address'} + ], + 'name': 'transferOwnership', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + {'name': '_blackListedUser', 'type': 'address'} + ], + 'name': 'destroyBlackFunds', + 'outputs': [], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'inputs': [ + {'name': '_initialSupply', 'type': 'uint256'}, + {'name': '_name', 'type': 'string'}, + {'name': '_symbol', 'type': 'string'}, + {'name': '_decimals', 'type': 'uint256'} + ], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'constructor' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'Issue', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'amount', 'type': 'uint256'} + ], + 'name': 'Redeem', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'newAddress', 'type': 'address'} + ], + 'name': 'Deprecate', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': 'feeBasisPoints', 'type': 'uint256'}, + {'indexed': false, 'name': 'maxFee', 'type': 'uint256'} + ], + 'name': 'Params', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': '_blackListedUser', 'type': 'address'}, + {'indexed': false, 'name': '_balance', 'type': 'uint256'} + ], + 'name': 'DestroyedBlackFunds', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': '_user', 'type': 'address'} + ], + 'name': 'AddedBlackList', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': false, 'name': '_user', 'type': 'address'} + ], + 'name': 'RemovedBlackList', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': true, 'name': 'owner', 'type': 'address'}, + {'indexed': true, 'name': 'spender', 'type': 'address'}, + {'indexed': false, 'name': 'value', 'type': 'uint256'} + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + {'indexed': true, 'name': 'from', 'type': 'address'}, + {'indexed': true, 'name': 'to', 'type': 'address'}, + {'indexed': false, 'name': 'value', 'type': 'uint256'} + ], + 'name': 'Transfer', + 'type': 'event' + }, + {'anonymous': false, 'inputs': [], 'name': 'Pause', 'type': 'event'}, + {'anonymous': false, 'inputs': [], 'name': 'Unpause', 'type': 'event'} + ]; +} diff --git a/packages/reown_appkit/example/base/lib/utils/test_data.dart b/packages/reown_appkit/example/base/lib/utils/test_data.dart index cb78e1d3..636ea9ac 100644 --- a/packages/reown_appkit/example/base/lib/utils/test_data.dart +++ b/packages/reown_appkit/example/base/lib/utils/test_data.dart @@ -36,6 +36,166 @@ String testSignTypedData(String address) => jsonEncode( const typedData = r'''{"types":{"EIP712Domain":[{"type":"string","name":"name"},{"type":"string","name":"version"},{"type":"uint256","name":"chainId"},{"type":"address","name":"verifyingContract"}],"Part":[{"name":"account","type":"address"},{"name":"value","type":"uint96"}],"Mint721":[{"name":"tokenId","type":"uint256"},{"name":"tokenURI","type":"string"},{"name":"creators","type":"Part[]"},{"name":"royalties","type":"Part[]"}]},"domain":{"name":"Mint721","version":"1","chainId":4,"verifyingContract":"0x2547760120aed692eb19d22a5d9ccfe0f7872fce"},"primaryType":"Mint721","message":{"@type":"ERC721","contract":"0x2547760120aed692eb19d22a5d9ccfe0f7872fce","tokenId":"1","uri":"ipfs://ipfs/hash","creators":[{"account":"0xc5eac3488524d577a1495492599e8013b1f91efa","value":10000}],"royalties":[],"tokenURI":"ipfs://ipfs/hash"}}'''; +Map typeDataV3(int chainId) => { + 'types': { + 'EIP712Domain': [ + {'name': 'name', 'type': 'string'}, + {'name': 'version', 'type': 'string'}, + {'name': 'chainId', 'type': 'uint256'}, + {'name': 'verifyingContract', 'type': 'address'} + ], + 'Person': [ + {'name': 'name', 'type': 'string'}, + {'name': 'wallet', 'type': 'address'} + ], + 'Mail': [ + {'name': 'from', 'type': 'Person'}, + {'name': 'to', 'type': 'Person'}, + {'name': 'contents', 'type': 'string'} + ] + }, + 'primaryType': 'Mail', + 'domain': { + 'name': 'Ether Mail', + 'version': '1', + 'chainId': chainId, + 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + 'message': { + 'from': { + 'name': 'Cow', + 'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' + }, + 'to': { + 'name': 'Bob', + 'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' + }, + 'contents': 'Hello, Bob!' + } + }; + +Map typeDataV4(int chainId) => { + 'types': { + 'EIP712Domain': [ + {'type': 'string', 'name': 'name'}, + {'type': 'string', 'name': 'version'}, + {'type': 'uint256', 'name': 'chainId'}, + {'type': 'address', 'name': 'verifyingContract'} + ], + 'Part': [ + {'name': 'account', 'type': 'address'}, + {'name': 'value', 'type': 'uint96'} + ], + 'Mint721': [ + {'name': 'tokenId', 'type': 'uint256'}, + {'name': 'tokenURI', 'type': 'string'}, + {'name': 'creators', 'type': 'Part[]'}, + {'name': 'royalties', 'type': 'Part[]'} + ] + }, + 'domain': { + 'name': 'Mint721', + 'version': '1', + 'chainId': chainId, + 'verifyingContract': '0x2547760120aed692eb19d22a5d9ccfe0f7872fce' + }, + 'primaryType': 'Mint721', + 'message': { + '@type': 'ERC721', + 'contract': '0x2547760120aed692eb19d22a5d9ccfe0f7872fce', + 'tokenId': '1', + 'uri': 'ipfs://ipfs/hash', + 'creators': [ + { + 'account': '0xc5eac3488524d577a1495492599e8013b1f91efa', + 'value': 10000 + } + ], + 'royalties': [], + 'tokenURI': 'ipfs://ipfs/hash' + } + }; + +/// KADENA /// + +// SignRequest createSignRequest({ +// required String networkId, +// required String signingPubKey, +// required String sender, +// String code = '"hello"', +// Map? data, +// List caps = const [], +// String chainId = '1', +// int gasLimit = 2000, +// double gasPrice = 1e-8, +// int ttl = 600, +// }) => +// SignRequest( +// code: code, +// data: data ?? {}, +// sender: sender, +// networkId: networkId, +// chainId: chainId, +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// signingPubKey: signingPubKey, +// ttl: ttl, +// caps: caps, +// ); + +// PactCommandPayload createPactCommandPayload({ +// required String networkId, +// required String sender, +// String code = '"hello"', +// Map? data, +// List signerCaps = const [], +// String chainId = '1', +// int gasLimit = 2000, +// double gasPrice = 1e-8, +// int ttl = 600, +// }) => +// PactCommandPayload( +// networkId: networkId, +// payload: CommandPayload( +// exec: ExecMessage( +// code: code, +// data: data ?? {}, +// ), +// ), +// signers: signerCaps, +// meta: CommandMetadata( +// chainId: chainId, +// gasLimit: gasLimit, +// gasPrice: gasPrice, +// ttl: ttl, +// sender: sender, +// ), +// ); + +// QuicksignRequest createQuicksignRequest({ +// required String cmd, +// List sigs = const [], +// }) => +// QuicksignRequest( +// commandSigDatas: [ +// CommandSigData( +// cmd: cmd, +// sigs: sigs, +// ), +// ], +// ); + +// GetAccountsRequest createGetAccountsRequest({ +// required String account, +// }) => +// GetAccountsRequest( +// accounts: [ +// AccountRequest( +// account: account, +// ), +// ], +// ); + /// KADENA /// // SignRequest createSignRequest({ diff --git a/packages/reown_appkit/example/base/lib/widgets/session_widget.dart b/packages/reown_appkit/example/base/lib/widgets/session_widget.dart index 93e09893..88b53997 100644 --- a/packages/reown_appkit/example/base/lib/widgets/session_widget.dart +++ b/packages/reown_appkit/example/base/lib/widgets/session_widget.dart @@ -14,23 +14,33 @@ import 'package:reown_appkit_dapp/widgets/method_dialog.dart'; class SessionWidget extends StatefulWidget { const SessionWidget({ super.key, - required this.session, - required this.appKit, + required this.sessionTopic, + required this.appKitModal, }); - final SessionData session; - final ReownAppKit appKit; + final String sessionTopic; + final ReownAppKitModal appKitModal; @override SessionWidgetState createState() => SessionWidgetState(); } class SessionWidgetState extends State { + late IReownAppKit _appKit; + late SessionData _session; + + @override + void initState() { + super.initState(); + _appKit = widget.appKitModal.appKit!; + _session = _appKit.sessions.get(widget.sessionTopic)!; + } + @override Widget build(BuildContext context) { final List children = [ Text( - '${StringConstants.sessionTopic}${widget.session.topic}', + '${StringConstants.sessionTopic}${_session.topic}', ), ]; @@ -38,7 +48,7 @@ class SessionWidgetState extends State { final List namespaceAccounts = []; // Loop through the namespaces, and get the accounts - for (final Namespace namespace in widget.session.namespaces.values) { + for (final Namespace namespace in _session.namespaces.values) { namespaceAccounts.addAll(namespace.accounts); } @@ -61,8 +71,8 @@ class SessionWidgetState extends State { ), child: ElevatedButton( onPressed: () async { - await widget.appKit.disconnectSession( - topic: widget.session.topic, + await _appKit.disconnectSession( + topic: _session.topic, reason: Errors.getSdkError( Errors.USER_DISCONNECTED, ).toSignError(), @@ -170,7 +180,7 @@ class SessionWidgetState extends State { final List buttons = []; // Add Methods for (final String method in getChainMethods(chainMetadata.type)) { - final namespaces = widget.session.namespaces[chainMetadata.type.name]; + final namespaces = _session.namespaces[chainMetadata.type.name]; final supported = namespaces?.methods.contains(method) ?? false; buttons.add( Container( @@ -226,33 +236,32 @@ class SessionWidgetState extends State { switch (chainMetadata.type) { case ChainType.eip155: return EIP155.callMethod( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, method: method, chainData: chainMetadata, address: address, ); case ChainType.polkadot: return Polkadot.callMethod( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, method: method, - chainId: chainMetadata.chainId, + chainData: chainMetadata, address: address, ); case ChainType.solana: return Solana.callMethod( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, method: method, chainData: chainMetadata, address: address, - isV0: true, ); // case ChainType.kadena: // return Kadena.callMethod( - // appKit: widget.appKit, - // topic: widget.session.topic, + // appKit: _appKit, + // topic: _session.topic, // method: method.toKadenaMethod()!, // chainId: chainMetadata.chainId, // address: address.toLowerCase(), @@ -264,9 +273,9 @@ class SessionWidgetState extends State { void _launchWallet() { if (kIsWeb) return; - widget.appKit.redirectToWallet( - topic: widget.session.topic, - redirect: widget.session.peer.metadata.redirect, + _appKit.redirectToWallet( + topic: _session.topic, + redirect: _session.peer.metadata.redirect, ); } @@ -284,8 +293,8 @@ class SessionWidgetState extends State { onPressed: enabled ? () async { final future = EIP155.callSmartContract( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, address: address, action: 'read', ); @@ -326,8 +335,8 @@ class SessionWidgetState extends State { onPressed: enabled ? () async { final future = EIP155.callSmartContract( - appKit: widget.appKit, - topic: widget.session.topic, + appKit: _appKit, + topic: _session.topic, address: address, action: 'write', ); diff --git a/packages/reown_appkit/example/base/pubspec.yaml b/packages/reown_appkit/example/base/pubspec.yaml index 1934ff9b..f928b0e4 100644 --- a/packages/reown_appkit/example/base/pubspec.yaml +++ b/packages/reown_appkit/example/base/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: sdk: flutter intl: ^0.19.0 json_annotation: ^4.8.1 - package_info_plus: ^7.0.0 + package_info_plus: ^8.0.2 qr_flutter: ^4.0.0 reown_appkit: path: ../.. diff --git a/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml b/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml index a56e8f21..09087a0e 100644 --- a/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml +++ b/packages/reown_appkit/example/modal/android/app/src/main/AndroidManifest.xml @@ -52,8 +52,8 @@ - - + + + + + + + + + + + + + + + + + + diff --git a/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt b/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt index 4094c0c7..877b2caa 100644 --- a/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt +++ b/packages/reown_appkit/example/modal/android/app/src/main/kotlin/com/example/sign/MainActivity.kt @@ -1,6 +1,65 @@ package com.web3modal.flutterExample import io.flutter.embedding.android.FlutterActivity +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { + private val eventsChannel = "com.web3modal.flutterExample/events" + private val methodsChannel = "com.web3modal.flutterExample/methods" + + private var initialLink: String? = null + private var linksReceiver: BroadcastReceiver? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val intent: Intent? = intent + initialLink = intent?.data?.toString() + + EventChannel(flutterEngine?.dartExecutor?.binaryMessenger, eventsChannel).setStreamHandler( + object : EventChannel.StreamHandler { + override fun onListen(args: Any?, events: EventChannel.EventSink) { + linksReceiver = createChangeReceiver(events) + } + override fun onCancel(args: Any?) { + linksReceiver = null + } + } + ) + + MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, methodsChannel).setMethodCallHandler { call, result -> + if (call.method == "initialLink") { + if (initialLink != null) { + result.success(initialLink) + } + } + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + if (intent.action === Intent.ACTION_VIEW) { + linksReceiver?.onReceive(this.applicationContext, intent) + } + } + + fun createChangeReceiver(events: EventChannel.EventSink): BroadcastReceiver? { + return object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val dataString = intent.dataString ?: + events.error("UNAVAILABLE", "Link unavailable", null) + events.success(dataString) + } + } + } } diff --git a/packages/reown_appkit/example/modal/ios/Podfile b/packages/reown_appkit/example/modal/ios/Podfile index 3e44f9c6..9a3af86b 100644 --- a/packages/reown_appkit/example/modal/ios/Podfile +++ b/packages/reown_appkit/example/modal/ios/Podfile @@ -40,5 +40,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end end end diff --git a/packages/reown_appkit/example/modal/ios/Podfile.lock b/packages/reown_appkit/example/modal/ios/Podfile.lock index 35be0e97..0d8a4669 100644 --- a/packages/reown_appkit/example/modal/ios/Podfile.lock +++ b/packages/reown_appkit/example/modal/ios/Podfile.lock @@ -84,6 +84,6 @@ SPEC CHECKSUMS: url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 -PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 +PODFILE CHECKSUM: 0a7d5b7d0e53420cb0284f7b2f171f93843b94d2 COCOAPODS: 1.15.2 diff --git a/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift b/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift index 81aa14a0..242bf151 100644 --- a/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift +++ b/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift @@ -4,11 +4,47 @@ import CoinbaseWalletSDK @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { + + private static let EVENTS_CHANNEL = "com.web3modal.flutterExample/events" + private static let METHODS_CHANNEL = "com.web3modal.flutterExample/methods" + + private var eventsChannel: FlutterEventChannel? + private var methodsChannel: FlutterMethodChannel? + var initialLink: String? + + private let linkStreamHandler = LinkStreamHandler() + + override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { GeneratedPluginRegistrant.register(with: self) + + let controller = window.rootViewController as! FlutterViewController + eventsChannel = FlutterEventChannel(name: AppDelegate.EVENTS_CHANNEL, binaryMessenger: controller.binaryMessenger) + eventsChannel?.setStreamHandler(linkStreamHandler) + + methodsChannel = FlutterMethodChannel(name: AppDelegate.METHODS_CHANNEL, binaryMessenger: controller.binaryMessenger) + methodsChannel?.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in + if (call.method == "initialLink") { + if let link = self?.initialLink { + let handled = self?.linkStreamHandler.handleLink(link) + if (handled == true) { + self?.initialLink = nil + } + } + } + }) + + // Add your deep link handling logic here + if let url = launchOptions?[.url] as? URL { + self.initialLink = url.absoluteString + } + + if let userActivityDictionary = launchOptions?[.userActivityDictionary] as? [String: Any], + let userActivity = userActivityDictionary["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity, + userActivity.activityType == NSUserActivityTypeBrowsingWeb { + + handleIncomingUniversalLink(userActivity: userActivity) + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @@ -21,19 +57,60 @@ import CoinbaseWalletSDK } } - return super.application(app, open: url, options: options) + return linkStreamHandler.handleLink(url.absoluteString) } override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if #available(iOS 13.0, *) { if (CoinbaseWalletSDK.isConfigured == true) { - if let url = userActivity.webpageURL, - (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true { + if let url = userActivity.webpageURL, (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true { return true } } } - return super.application(application, continue: userActivity, restorationHandler: restorationHandler) + if userActivity.activityType == NSUserActivityTypeBrowsingWeb { + handleIncomingUniversalLink(userActivity: userActivity) + return true + } + + return false + } + + private func handleIncomingUniversalLink(userActivity: NSUserActivity) { + if let url = userActivity.webpageURL { + // Handle the URL, navigate to appropriate screen + print("App launched with Universal Link: \(url.absoluteString)") + let handled = linkStreamHandler.handleLink(url.absoluteString) + if (!handled){ + self.initialLink = url.absoluteString + } + } } } + +class LinkStreamHandler: NSObject, FlutterStreamHandler { + var eventSink: FlutterEventSink? + var queuedLinks = [String]() + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = events + queuedLinks.forEach({ events($0) }) + queuedLinks.removeAll() + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + self.eventSink = nil + return nil + } + + func handleLink(_ link: String) -> Bool { + guard let eventSink = eventSink else { + queuedLinks.append(link) + return false + } + eventSink(link) + return true + } +} \ No newline at end of file diff --git a/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements b/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements index 903def2a..bfe855c1 100644 --- a/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements +++ b/packages/reown_appkit/example/modal/ios/Runner/Runner.entitlements @@ -2,7 +2,9 @@ - aps-environment - development + com.apple.developer.associated-domains + + applinks:appkit-lab.reown.com + diff --git a/packages/reown_appkit/example/modal/lib/home_page.dart b/packages/reown_appkit/example/modal/lib/home_page.dart index a6744596..a2a44907 100644 --- a/packages/reown_appkit/example/modal/lib/home_page.dart +++ b/packages/reown_appkit/example/modal/lib/home_page.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:fl_toast/fl_toast.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:reown_appkit_example/services/deep_link_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:reown_appkit/reown_appkit.dart'; @@ -58,15 +59,14 @@ class _MyHomePageState extends State { final internal = widget.bundleId.endsWith('.internal'); final debug = widget.bundleId.endsWith('.debug'); if (internal || debug || kDebugMode) { - return 'internal'; + return '-internal'; } return ''; } String _universalLink() { - // TODO change /flutter_appkit to something else - Uri link = Uri.parse('https://appkit-lab.reown.com/flutter_appkit'); - if (_flavor.isNotEmpty) { + Uri link = Uri.parse('https://appkit-lab.reown.com/flutter_appkit_modal'); + if (_flavor.isNotEmpty && !kDebugMode) { return link.replace(path: '${link.path}_internal').toString(); } return link.toString(); @@ -88,7 +88,7 @@ class _MyHomePageState extends State { description: StringConstants.pageTitle, url: _universalLink(), icons: [ - 'https://docs.walletconnect.com/assets/images/web3modalLogo-2cee77e07851ba0a710b56d03d4d09dd.png' + 'https://raw.githubusercontent.com/reown-com/reown_flutter/refs/heads/develop/assets/appkit_logo.png', ], redirect: _constructRedirect(), ); @@ -218,7 +218,16 @@ class _MyHomePageState extends State { metadata: _pairingMetadata(), siweConfig: _siweConfig(siweAuthValue), enableAnalytics: analyticsValue, // OPTIONAL - null by default - enableEmail: emailWalletValue, // OPTIONAL - false by default + featuresConfig: FeaturesConfig( + email: emailWalletValue, + socials: [ + AppKitSocialOption.Farcaster, + AppKitSocialOption.X, + AppKitSocialOption.Apple, + AppKitSocialOption.Discord, + ], + showMainWallets: true, // OPTIONAL - true by default + ), // requiredNamespaces: {}, // optionalNamespaces: {}, // includedWalletIds: {}, @@ -263,11 +272,16 @@ class _MyHomePageState extends State { _appKitModal.appKit!.core.addLogListener(_logListener); // await _appKitModal.init(); + + DeepLinkHandler.init(_appKitModal); + DeepLinkHandler.checkInitialLink(); + setState(() {}); } - void _logListener(LogEvent event) { - if (event.level == Level.debug || event.level == Level.error) { + void _logListener(event) { + if ('${event.level}' == 'Level.debug' || + '${event.level}' == 'Level.error') { // TODO send to mixpanel log('${event.message}'); } else { @@ -367,6 +381,9 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { + if (!_initialized) { + return SizedBox.shrink(); + } return Scaffold( backgroundColor: ReownAppKitModalTheme.colorsOf(context).background125, appBar: AppBar( @@ -396,6 +413,7 @@ class _MyHomePageState extends State { toggleOverlay: _toggleOverlay, toggleBrightness: widget.toggleBrightness, toggleTheme: widget.toggleTheme, + appKitModal: _appKitModal, ), ), onEndDrawerChanged: (isOpen) { @@ -496,6 +514,20 @@ class _ConnectedView extends StatelessWidget { // }, // ), ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppKitModalBalanceButton( + appKitModal: appKit, + onTap: appKit.openModalView, + ), + const SizedBox.square(dimension: 8.0), + AppKitModalAddressButton( + appKitModal: appKit, + onTap: appKit.openModalView, + ), + ], + ), SessionWidget(appKit: appKit), const SizedBox.square(dimension: 12.0), ], diff --git a/packages/reown_appkit/example/modal/lib/main.dart b/packages/reown_appkit/example/modal/lib/main.dart index f4d292da..7142b461 100644 --- a/packages/reown_appkit/example/modal/lib/main.dart +++ b/packages/reown_appkit/example/modal/lib/main.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; import 'package:reown_appkit_example/home_page.dart'; +import 'package:reown_appkit_example/services/deep_link_handler.dart'; import 'package:reown_appkit_example/utils/constants.dart'; import 'package:reown_appkit/reown_appkit.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); + DeepLinkHandler.initListener(); runApp(const MyApp()); } diff --git a/packages/reown_appkit/example/modal/lib/services/deep_link_handler.dart b/packages/reown_appkit/example/modal/lib/services/deep_link_handler.dart new file mode 100644 index 00000000..afcc105c --- /dev/null +++ b/packages/reown_appkit/example/modal/lib/services/deep_link_handler.dart @@ -0,0 +1,60 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; + +class DeepLinkHandler { + static const _methodChannel = MethodChannel( + 'com.web3modal.flutterExample/methods', + ); + static const _eventChannel = EventChannel( + 'com.web3modal.flutterExample/events', + ); + static final waiting = ValueNotifier(false); + static late IReownAppKitModal _appKitModal; + + static void initListener() { + if (kIsWeb) return; + try { + _eventChannel.receiveBroadcastStream().listen( + _onLink, + onError: _onError, + ); + } catch (e) { + debugPrint('[SampleModal] initListener $e'); + } + } + + static void init(IReownAppKitModal appKitModal) { + if (kIsWeb) return; + _appKitModal = appKitModal; + } + + static void checkInitialLink() async { + if (kIsWeb) return; + try { + _methodChannel.invokeMethod('initialLink'); + } catch (e) { + debugPrint('[SampleModal] checkInitialLink $e'); + } + } + + static Uri get nativeUri => + Uri.parse(_appKitModal.appKit!.metadata.redirect?.native ?? ''); + static Uri get universalUri => + Uri.parse(_appKitModal.appKit!.metadata.redirect?.universal ?? ''); + static String get host => universalUri.host; + + static void _onLink(dynamic link) async { + debugPrint('[SampleModal] _onLink $link'); + if (link == null) return; + final handled = await _appKitModal.dispatchEnvelope(link); + if (!handled) { + debugPrint('[SampleModal] _onLink not handled by AppKit'); + } + } + + static void _onError(dynamic error) { + debugPrint('[SampleModal] _onError $error'); + waiting.value = false; + } +} diff --git a/packages/reown_appkit/example/modal/lib/services/eip155_service.dart b/packages/reown_appkit/example/modal/lib/services/eip155_service.dart index 4fd8c3b1..bcc1a3ae 100644 --- a/packages/reown_appkit/example/modal/lib/services/eip155_service.dart +++ b/packages/reown_appkit/example/modal/lib/services/eip155_service.dart @@ -66,7 +66,7 @@ class EIP155 { } static Future callMethod({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String topic, required EIP155UIMethods method, required String chainId, @@ -76,32 +76,32 @@ class EIP155 { switch (method) { case EIP155UIMethods.requestAccounts: return requestAccounts( - appKit: appKit, + appKitModal: appKitModal, ); case EIP155UIMethods.personalSign: return personalSign( - appKit: appKit, + appKitModal: appKitModal, message: testSignData, ); case EIP155UIMethods.ethSignTypedDataV3: return ethSignTypedDataV3( - appKit: appKit, + appKitModal: appKitModal, data: jsonEncode(typeDataV3(cid)), ); case EIP155UIMethods.ethSignTypedData: return ethSignTypedData( - appKit: appKit, + appKitModal: appKitModal, data: jsonEncode(typedData()), ); case EIP155UIMethods.ethSignTypedDataV4: return ethSignTypedDataV4( - appKit: appKit, + appKitModal: appKitModal, data: jsonEncode(typeDataV4(cid)), ); case EIP155UIMethods.ethSignTransaction: case EIP155UIMethods.ethSendTransaction: return ethSendOrSignTransaction( - appKit: appKit, + appKitModal: appKitModal, method: method, transaction: Transaction( from: EthereumAddress.fromHex(address), @@ -114,17 +114,17 @@ class EIP155 { ); case EIP155UIMethods.walletWatchAsset: return walletWatchAsset( - appKit: appKit, + appKitModal: appKitModal, ); } } static Future requestAccounts({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: EIP155UIMethods.requestAccounts.name, params: [], @@ -133,84 +133,84 @@ class EIP155 { } static Future personalSign({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String message, }) async { final bytes = utf8.encode(message); final encoded = hex.encode(bytes); - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: EIP155UIMethods.personalSign.name, params: [ '0x$encoded', - appKit.session!.address!, + appKitModal.session!.address!, ], ), ); } static Future ethSignTypedData({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String data, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: EIP155UIMethods.ethSignTypedData.name, params: [ data, - appKit.session!.address!, + appKitModal.session!.address!, ], ), ); } static Future ethSignTypedDataV3({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String data, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: EIP155UIMethods.ethSignTypedDataV3.name, params: [ data, - appKit.session!.address!, + appKitModal.session!.address!, ], ), ); } static Future ethSignTypedDataV4({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String data, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: EIP155UIMethods.ethSignTypedDataV4.name, params: [ data, - appKit.session!.address!, + appKitModal.session!.address!, ], ), ); } static Future ethSendOrSignTransaction({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required Transaction transaction, required EIP155UIMethods method, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: method.name, params: [ @@ -221,11 +221,11 @@ class EIP155 { } static Future walletWatchAsset({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, }) async { - return await appKit.request( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return await appKitModal.request( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, request: SessionRequestParams( method: EIP155UIMethods.walletWatchAsset.name, params: { @@ -244,7 +244,7 @@ class EIP155 { // Example of calling `transfer` function from AAVE token Smart Contract static Future callTestSmartContract({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String action, }) async { // Create DeployedContract object using contract's ABI and address @@ -259,7 +259,7 @@ class EIP155 { switch (action) { case 'read': return _readSmartContract( - appKit: appKit, + appKitModal: appKitModal, contract: deployedContract, ); case 'write': @@ -276,22 +276,22 @@ class EIP155 { // ); // we first call `decimals` function, which is a read function, // to check how much decimal we need to use to parse the amount value - final decimals = await appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + final decimals = await appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: deployedContract, functionName: 'decimals', ); final d = (decimals.first as BigInt); final requestValue = _formatValue(0.01, decimals: d); // now we call `transfer` write function with the parsed value. - return appKit.requestWriteContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return appKitModal.requestWriteContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: deployedContract, functionName: 'transfer', transaction: Transaction( - from: EthereumAddress.fromHex(appKit.session!.address!), + from: EthereumAddress.fromHex(appKitModal.session!.address!), ), parameters: [ EthereumAddress.fromHex( @@ -327,7 +327,7 @@ class EIP155 { // Example of calling `transfer` function from USDT token Smart Contract static Future callUSDTSmartContract({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required String action, }) async { // Create DeployedContract object using contract's ABI and address @@ -342,28 +342,28 @@ class EIP155 { switch (action) { case 'read': return _readSmartContract( - appKit: appKit, + appKitModal: appKitModal, contract: deployedContract, ); case 'write': // we first call `decimals` function, which is a read function, // to check how much decimal we need to use to parse the amount value - final decimals = await appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + final decimals = await appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: deployedContract, functionName: 'decimals', ); final d = (decimals.first as BigInt); final requestValue = _formatValue(0.23, decimals: d); // now we call `transfer` write function with the parsed value. - return appKit.requestWriteContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + return appKitModal.requestWriteContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: deployedContract, functionName: 'transfer', transaction: Transaction( - from: EthereumAddress.fromHex(appKit.session!.address!), + from: EthereumAddress.fromHex(appKitModal.session!.address!), ), parameters: [ EthereumAddress.fromHex( @@ -378,38 +378,38 @@ class EIP155 { } static Future _readSmartContract({ - required ReownAppKitModal appKit, + required ReownAppKitModal appKitModal, required DeployedContract contract, }) async { final results = await Future.wait([ // results[0] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'name', ), // results[1] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'totalSupply', ), // results[2] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'balanceOf', parameters: [ - EthereumAddress.fromHex(appKit.session!.address!), + EthereumAddress.fromHex(appKitModal.session!.address!), ], ), // results[4] - appKit.requestReadContract( - topic: appKit.session!.topic, - chainId: appKit.selectedChain!.chainId, + appKitModal.requestReadContract( + topic: appKitModal.session!.topic, + chainId: appKitModal.selectedChain!.chainId, deployedContract: contract, functionName: 'decimals', ), diff --git a/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart b/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart index 4cc98f10..7db646ee 100644 --- a/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart +++ b/packages/reown_appkit/example/modal/lib/widgets/debug_drawer.dart @@ -1,24 +1,32 @@ +// ignore_for_file: depend_on_referenced_packages + import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -// ignore: depend_on_referenced_packages + import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:reown_appkit/version.dart' as apkt; import 'package:reown_appkit/reown_appkit.dart'; +import 'package:reown_appkit/version.dart' as apkt_v; + +import 'package:reown_sign/version.dart' as sign_v; +import 'package:reown_core/version.dart' as core_v; + class DebugDrawer extends StatefulWidget { const DebugDrawer({ super.key, required this.toggleOverlay, required this.toggleBrightness, required this.toggleTheme, + required this.appKitModal, }); final VoidCallback toggleOverlay; final VoidCallback toggleBrightness; final VoidCallback toggleTheme; + final ReownAppKitModal appKitModal; @override State createState() => _DebugDrawerState(); @@ -234,32 +242,105 @@ class _DebugDrawerState extends State with WidgetsBindingObserver { ], ), ), - FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) { - return InkWell( - onTap: () { - Clipboard.setData( - ClipboardData( - text: - '${snapshot.data?.packageName} v${snapshot.data?.version ?? ''} (${snapshot.data?.buildNumber})\n' - 'AppKit v${apkt.packageVersion}\n' - 'Core v$packageVersion', - ), - ); - }, - child: Text( - '${snapshot.data?.packageName} v${snapshot.data?.version ?? ''} (${snapshot.data?.buildNumber})\n' - 'AppKit v${apkt.packageVersion}\n' - 'Core v$packageVersion', + const SizedBox(height: 16.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Redirect:', + textAlign: TextAlign.left, style: TextStyle( fontSize: 12.0, - color: - ReownAppKitModalTheme.colorsOf(context).foreground100, + fontWeight: FontWeight.bold, ), ), - ); - }, + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Native: ', + style: TextStyle(fontSize: 12.0), + ), + Expanded( + child: Text( + '${widget.appKitModal.appKit!.metadata.redirect?.native}', + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Universal: ', + style: TextStyle(fontSize: 12.0), + ), + Expanded( + child: Text( + '${widget.appKitModal.appKit!.metadata.redirect?.universal}', + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ), + ], + ), + Row( + children: [ + const Text( + 'Link Mode: ', + style: TextStyle(fontSize: 12.0), + ), + Text( + '${widget.appKitModal.appKit!.metadata.redirect?.linkMode}', + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ], + ), + const Divider(height: 10.0), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + final versionText = + '${snapshot.data?.packageName} v${snapshot.data?.version ?? ''} (${snapshot.data?.buildNumber})\n' + 'AppKit v${apkt_v.packageVersion}\n' + 'Sign v${sign_v.packageVersion}\n' + 'Core v${core_v.packageVersion}'; + return InkWell( + onTap: () => Clipboard.setData(ClipboardData( + text: versionText, + )), + child: Text( + versionText, + style: TextStyle( + fontSize: 12.0, + color: ReownAppKitModalTheme.colorsOf(context) + .foreground100, + ), + ), + ); + }, + ), + ], + ), ), const SizedBox.square(dimension: 10.0), const Divider(height: 1.0, indent: 12.0, endIndent: 12.0), diff --git a/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart b/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart index ed8fb458..10728f2e 100644 --- a/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart +++ b/packages/reown_appkit/example/modal/lib/widgets/session_widget.dart @@ -165,15 +165,8 @@ class SessionWidgetState extends State { } Widget _buildAccountWidget(String account) { - // final chainId = NamespaceUtils.getChainFromAccount(account); - // final chainMetadata = ChainDataWrapper.getChainMetadataFromChain( - // chainId.split(':').first, - // chainId.split(':').last, - // ); - final List children = [ Text( - // chainMetadata.appKitNetworkInfo.name, widget.appKit.selectedChain?.name ?? 'Unsupported chain', style: ReownAppKitModalTheme.getDataOf(context) .textStyles @@ -271,12 +264,9 @@ class SessionWidgetState extends State { onPressed: implemented ? () async { widget.appKit.launchConnectedWallet(); - final future = callChainMethod( - // chainMetadata.type, - EIP155.methodFromName(method) - // chainMetadata, - // address, - ); + final future = callChainMethod(EIP155.methodFromName( + method, + )); MethodDialog.show(context, method, future); } : null, @@ -315,7 +305,7 @@ class SessionWidgetState extends State { onPressed: onSepolia ? () async { final future = EIP155.callTestSmartContract( - appKit: widget.appKit, + appKitModal: widget.appKit, action: 'read', ); MethodDialog.show( @@ -327,7 +317,7 @@ class SessionWidgetState extends State { : onMainnet ? () async { final future = EIP155.callUSDTSmartContract( - appKit: widget.appKit, + appKitModal: widget.appKit, action: 'read', ); MethodDialog.show( @@ -352,7 +342,7 @@ class SessionWidgetState extends State { ? () async { widget.appKit.launchConnectedWallet(); final future = EIP155.callTestSmartContract( - appKit: widget.appKit, + appKitModal: widget.appKit, action: 'write', ); MethodDialog.show(context, 'Test Contract (Write)', future); @@ -361,7 +351,7 @@ class SessionWidgetState extends State { ? () async { widget.appKit.launchConnectedWallet(); final future = EIP155.callUSDTSmartContract( - appKit: widget.appKit, + appKitModal: widget.appKit, action: 'write', ); MethodDialog.show( @@ -457,15 +447,10 @@ class SessionWidgetState extends State { ); } - Future callChainMethod( - // ChainType type, - EIP155UIMethods method, - // ChainMetadata chainMetadata, - // String address, - ) { + Future callChainMethod(EIP155UIMethods method) { final session = widget.appKit.session!; return EIP155.callMethod( - appKit: widget.appKit, + appKitModal: widget.appKit, topic: session.topic ?? '', method: method, chainId: widget.appKit.selectedChain!.chainId, diff --git a/packages/reown_appkit/generate_files.sh b/packages/reown_appkit/generate_files.sh index 2356ead3..b1b02877 100644 --- a/packages/reown_appkit/generate_files.sh +++ b/packages/reown_appkit/generate_files.sh @@ -20,6 +20,7 @@ dart run dependency_validator cd ios +# rm Podfile.lock # pod deintegrate # pod cache clean -all pod install @@ -42,6 +43,7 @@ dart run dependency_validator cd ios +# rm Podfile.lock # pod deintegrate # pod cache clean -all pod install diff --git a/packages/reown_appkit/lib/appkit_modal.dart b/packages/reown_appkit/lib/appkit_modal.dart index 04bc6714..b9f37fb7 100644 --- a/packages/reown_appkit/lib/appkit_modal.dart +++ b/packages/reown_appkit/lib/appkit_modal.dart @@ -5,20 +5,20 @@ // platforms in the `pubspec.yaml` at // https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. -/// Models +// Models export 'modal/models/public/appkit_modal_models.dart'; -/// Theme +// Theme export 'modal/theme/public/appkit_modal_theme.dart'; -/// Utils +// Utils export 'modal/utils/public/appkit_modal_utils.dart'; -/// Widgets +// Widgets export 'modal/widgets/public/appkit_modal_widgets.dart'; -/// Pages +// Pages export 'modal/pages/public/appkit_modal_pages.dart'; -/// Services +// Services export 'modal/appkit_modal_impl.dart'; diff --git a/packages/reown_appkit/lib/base/appkit_base_impl.dart b/packages/reown_appkit/lib/base/appkit_base_impl.dart index fba8eedb..a4ce1eca 100644 --- a/packages/reown_appkit/lib/base/appkit_base_impl.dart +++ b/packages/reown_appkit/lib/base/appkit_base_impl.dart @@ -3,7 +3,9 @@ import 'package:reown_core/relay_client/websocket/http_client.dart'; import 'package:reown_core/store/generic_store.dart'; import 'package:reown_core/store/i_generic_store.dart'; +/// Base class that containes Core and SIgn used dapps developers to create UI-less interaction with WalletConnect protocol class ReownAppKit implements IReownAppKit { + /// static const List> DEFAULT_METHODS = [ [ MethodConstants.WC_SESSION_PROPOSE, @@ -17,6 +19,7 @@ class ReownAppKit implements IReownAppKit { bool _initialized = false; + /// Creates a instance of ReownAppKit to be used alone or to pass to ReownAppKitModal static Future createInstance({ required String projectId, String relayUrl = ReownConstants.DEFAULT_RELAY_URL, @@ -54,6 +57,7 @@ class ReownAppKit implements IReownAppKit { @override final PairingMetadata metadata; + /// ReownAppKit({ required this.core, required this.metadata, diff --git a/packages/reown_appkit/lib/base/i_appkit_base_impl.dart b/packages/reown_appkit/lib/base/i_appkit_base_impl.dart index 929e00af..9bfb963c 100644 --- a/packages/reown_appkit/lib/base/i_appkit_base_impl.dart +++ b/packages/reown_appkit/lib/base/i_appkit_base_impl.dart @@ -1,9 +1,14 @@ import 'package:reown_sign/i_sign_dapp.dart'; import 'package:reown_sign/reown_sign.dart'; +/// abstract class IReownAppKit implements IReownSignDapp { + /// final String protocol = 'wc'; + + /// final int version = 2; + /// abstract final IReownSign reOwnSign; } diff --git a/packages/reown_appkit/lib/modal/appkit_modal_impl.dart b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart index 32b6fbb4..1395a06f 100644 --- a/packages/reown_appkit/lib/modal/appkit_modal_impl.dart +++ b/packages/reown_appkit/lib/modal/appkit_modal_impl.dart @@ -1,21 +1,21 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; -import 'dart:developer' as dev; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_core/store/i_store.dart'; + +import 'package:reown_appkit/reown_appkit.dart'; import 'package:reown_appkit/modal/services/uri_service/launch_url_exception.dart'; import 'package:reown_appkit/modal/services/uri_service/url_utils.dart'; import 'package:reown_appkit/modal/services/uri_service/url_utils_singleton.dart'; import 'package:reown_appkit/modal/utils/core_utils.dart'; import 'package:reown_appkit/modal/utils/platform_utils.dart'; -import 'package:reown_appkit/reown_appkit.dart'; -import 'package:reown_core/store/i_store.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; - import 'package:reown_appkit/modal/constants/string_constants.dart'; import 'package:reown_appkit/modal/pages/account_page.dart'; import 'package:reown_appkit/modal/pages/approve_magic_request_page.dart'; @@ -32,7 +32,6 @@ import 'package:reown_appkit/modal/services/explorer_service/explorer_service.da import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; import 'package:reown_appkit/modal/services/magic_service/magic_service.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/modal/services/siwe_service/siwe_service.dart'; @@ -55,6 +54,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { Map _optionalNamespaces = {}; String? _lastChainEmitted; bool _supportsOneClickAuth = false; + bool _serviceInitialized = false; ReownAppKitModalStatus _status = ReownAppKitModalStatus.idle; @override @@ -93,10 +93,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { String? get avatarUrl => _avatarUrl; double? _chainBalance; + @Deprecated('Use balanceNotifier') @override - String get chainBalance { - return CoreUtils.formatChainBalance(_chainBalance); - } + String get chainBalance => CoreUtils.formatChainBalance(_chainBalance); @override final balanceNotifier = ValueNotifier('-.--'); @@ -113,8 +112,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { @override ReownAppKitModalSession? get session => _currentSession; - Logger get _logger => _appKit.core.logger; - IStore> get _storage => _appKit.core.storage; bool _disconnectOnClose = false; @@ -128,20 +125,23 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return null; } + @override + late final FeaturesConfig featuresConfig; + + /// ReownAppKitModal({ required BuildContext context, IReownAppKit? appKit, String? projectId, PairingMetadata? metadata, + bool? enableAnalytics, SIWEConfig? siweConfig, + FeaturesConfig? featuresConfig, Map? requiredNamespaces, Map? optionalNamespaces, Set? featuredWalletIds, Set? includedWalletIds, Set? excludedWalletIds, - bool? enableAnalytics, - bool enableEmail = false, - List blockchains = const [], LogLevel logLevel = LogLevel.nothing, }) { if (appKit == null) { @@ -156,11 +156,8 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } } - // if (siweConfig?.enabled == true && context == null) { - // throw ReownAppKitModalException( - // '`context:` parameter is required if using `siweConfig:`. Also, `context:` parameter will be enforced in future versions.', - // ); - // } + + this.featuresConfig = featuresConfig ?? FeaturesConfig(email: false); _context = context; @@ -197,10 +194,12 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { core: _appKit.core, ); - magicService.instance = MagicService( - core: _appKit.core, - metadata: _appKit.metadata, - enabled: enableEmail, + GetIt.I.registerSingleton( + MagicService( + core: _appKit.core, + metadata: _appKit.metadata, + featuresConfig: this.featuresConfig, + ), ); coinbaseService.instance = CoinbaseService( @@ -215,15 +214,32 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } + IMagicService get _magicService => GetIt.I(); + ////////* PUBLIC METHODS *///////// - bool _serviceInitialized = false; + @override + Future dispatchEnvelope(String url) async { + final envelope = ReownCoreUtils.getSearchParamFromURL(url, 'wc_ev'); + if (envelope.isNotEmpty) { + await _appKit.dispatchEnvelope(url); + return true; + } + + final state = ReownCoreUtils.getSearchParamFromURL(url, 'state'); + if (state.isNotEmpty) { + _magicService.completeSocialLogin(url: url); + return true; + } + + return false; + } @override Future init() async { _serviceInitialized = false; if (!CoreUtils.isValidProjectID(_projectId)) { - _logger.e( + _appKit.core.logger.e( '[$runtimeType] projectId $_projectId is invalid. ' 'Please provide a valid projectId. ' 'See ${UrlConstants.docsUrl}/appkit/flutter/core/options for details.', @@ -253,10 +269,10 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _currentSelectedChainId ??= _currentSession!.chainId; await _setSesionAndChainData(_currentSession!); if (isMagic) { - await magicService.instance.init(); + await _magicService.init(); } } else { - magicService.instance.init(); + _magicService.init(); } await expirePreviousInactivePairings(); @@ -280,7 +296,10 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { // There's a session stored if (wcSessions.isNotEmpty) { await _storeSession( - ReownAppKitModalSession(sessionData: wcSessions.first)); + ReownAppKitModalSession( + sessionData: wcSessions.first, + ), + ); // session should not outlive the pairing if (wcPairings.isEmpty) { await disconnect(); @@ -299,7 +318,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { // Every time the app gets killed Magic service will treat the user as disconnected // So we will need to treat magic session differently final email = _currentSession!.email; - magicService.instance.setEmail(email); + _magicService.setEmail(email); + final provider = _currentSession!.socialProvider; + _magicService.setProvider(provider); } else { await _cleanSession(); } @@ -316,7 +337,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _status = connected ? ReownAppKitModalStatus.initialized : ReownAppKitModalStatus.error; - _logger.i('[$runtimeType] initialized'); + _appKit.core.logger.i('[$runtimeType] initialized'); _notify(); } @@ -339,14 +360,13 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } - Future _setSesionAndChainData( - ReownAppKitModalSession modalSession) async { + Future _setSesionAndChainData(ReownAppKitModalSession sData) async { try { - await _storeSession(modalSession); - _currentSelectedChainId = _currentSelectedChainId ?? modalSession.chainId; + await _storeSession(sData); + _currentSelectedChainId = _currentSelectedChainId ?? sData.chainId; await _setLocalEthChain(_currentSelectedChainId!, logEvent: false); } catch (e, s) { - _logger.e( + _appKit.core.logger.d( '[$runtimeType] _setSesionAndChainData error $e', stackTrace: s, ); @@ -361,8 +381,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return ReownAppKitModalSession.fromMap(storedSession); } } - } catch (e) { - _logger.e('[$runtimeType] _getStoredSession error: $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] _getStoredSession error: $e', + stackTrace: s, + ); } return null; } @@ -374,8 +397,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { StorageConstants.modalSession, _currentSession!.toMap(), ); - } catch (e) { - _logger.e('[$runtimeType] _storeSession error: $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] _storeSession error: $e', + stackTrace: s, + ); } // _isConnected shoudl probably go at the very end of the connection _isConnected = true; @@ -419,7 +445,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _chainBalance = null; if (_currentSession?.sessionService.isMagic == true) { - await magicService.instance.switchNetwork(chainId: chainInfo.chainId); + await _magicService.switchNetwork(chainId: chainInfo.chainId); // onModalNetworkChange.broadcast(ModalNetworkChange( // chainId: chainInfo.namespace, // )); @@ -483,16 +509,20 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { Future _setLocalEthChain(String chainId, {bool? logEvent}) async { _currentSelectedChainId = chainId; final caip2Chain = '${CoreConstants.namespace}:$_currentSelectedChainId'; - _logger.i('[$runtimeType] set local chain $caip2Chain'); _currentSelectedChainId = chainId; _notify(); try { - await _storage.set( - StorageConstants.selectedChainId, - {'chainId': _currentSelectedChainId!}, + if (isConnected) { + await _storage.set( + StorageConstants.selectedChainId, + {'chainId': _currentSelectedChainId!}, + ); + } + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] _setLocalEthChain error: $e', + stackTrace: s, ); - } catch (e) { - _logger.e('[$runtimeType] _setLocalEthChain error: $e'); } if (_isConnected && logEvent == true) { analyticsService.instance.sendEvent(SwitchNetworkEvent( @@ -550,6 +580,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { KeyConstants.confirmEmailPage, KeyConstants.selectNetworkPage, KeyConstants.accountPage, + KeyConstants.socialLoginPage, ]; final List _allowedScreensWhenDisconnected = [ @@ -574,7 +605,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _context = context ?? modalContext; if (_context == null) { - _logger.e( + _appKit.core.logger.e( 'No context was found. ' 'Try adding `context:` parameter in ReownAppKitModal class', ); @@ -591,7 +622,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final isBottomSheet = PlatformUtils.isBottomSheet(); final theme = ReownAppKitModalTheme.maybeOf(_context!); - await magicService.instance.syncTheme(theme); + await _magicService.syncTheme(theme); final themeData = theme?.themeData ?? const ReownAppKitModalThemeData(); Widget? showWidget = startWidget; @@ -733,7 +764,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { onModalError.broadcast(WalletNotInstalled()); } else { if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined connection'); onModalError.broadcast(UserRejectedConnection()); analyticsService.instance.sendEvent(ConnectErrorEvent( message: 'User declined connection', @@ -746,15 +776,13 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } } else if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined connection'); onModalError.broadcast(UserRejectedConnection()); analyticsService.instance.sendEvent(ConnectErrorEvent( message: 'User declined connection', )); } else { - _logger.e( - '[$runtimeType] Error connecting wallet', - error: e, + _appKit.core.logger.e( + '[$runtimeType] connectSelectedWallet error: $e', stackTrace: s, ); onModalError.broadcast(ErrorOpeningWallet()); @@ -803,8 +831,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _notify(); _awaitConnectionCallback(connectResponse); } - } catch (e) { - _logger.e('[$runtimeType] buildConnectionUri $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] buildConnectionUri error: $e', + stackTrace: s, + ); } } } @@ -813,7 +844,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { try { final _ = await connectResponse.session.future; } on TimeoutException { - _logger.i('[$runtimeType] Rebuilding session, ending future'); + _appKit.core.logger.i('[$runtimeType] Rebuilding session, ending future'); return; } catch (e) { await _connectionErrorHandler(e); @@ -838,7 +869,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } } on TimeoutException { - _logger.i('[$runtimeType] Rebuilding session, ending future'); + _appKit.core.logger.i('[$runtimeType] Rebuilding session, ending future'); return; } catch (e) { await disconnect(); @@ -848,7 +879,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { Future _connectionErrorHandler(dynamic e) async { if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined connection'); onModalError.broadcast(UserRejectedConnection()); analyticsService.instance.sendEvent(ConnectErrorEvent( message: 'User declined connection', @@ -866,14 +896,12 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { analyticsService.instance.sendEvent(ConnectErrorEvent( message: message, )); - _logger.e('[$runtimeType] $message', error: e); } if (e is ReownSignError || e is ReownCoreError) { onModalError.broadcast(ModalError(e.message)); analyticsService.instance.sendEvent(ConnectErrorEvent( message: e.message, )); - _logger.e('[$runtimeType] ${e.message}', error: e); } } return await expirePreviousInactivePairings(); @@ -905,6 +933,14 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final metadataRedirect = _currentSession!.peer?.metadata.redirect; + final appLink = (metadataRedirect?.universal ?? ''); + final supportedApps = _appKit.core.getLinkModeSupportedApps(); + final isLinkMode = appLink.isNotEmpty && supportedApps.contains(appLink); + if (isLinkMode) { + // Opening peers during Link Mode requests is handled in Sign Engine + return; + } + final walletRedirect = explorerService.instance.getWalletRedirect( walletInfo, ); @@ -949,7 +985,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } if (_currentSession?.sessionService.isMagic == true) { await Future.delayed(Duration(milliseconds: 300)); - final disconnected = await magicService.instance.disconnect(); + final disconnected = await _magicService.disconnect(); if (!disconnected) { _status = ReownAppKitModalStatus.initialized; _notify(); @@ -1008,9 +1044,9 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { return; } _isOpen = false; + final currentKey = widgetStack.instance.getCurrent().key; if (_disconnectOnClose) { _disconnectOnClose = false; - final currentKey = widgetStack.instance.getCurrent().key; if (currentKey == KeyConstants.approveSiwePageKey) { analyticsService.instance.sendEvent(ClickCancelSiwe( network: _currentSelectedChainId ?? '', @@ -1036,10 +1072,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final blockExplorer = selectedChain!.explorerUrl; final address = _currentSession?.address ?? ''; final explorerUrl = '$blockExplorer/address/$address'; - await uriService.instance.launchUrl( - Uri.parse(explorerUrl), - mode: LaunchMode.externalApplication, - ); + await ReownCoreUtils.openURL(explorerUrl); } } @@ -1068,7 +1101,8 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } // - _logger.d('[$runtimeType] requestWriteContract, chainId: $reqChainId'); + _appKit.core.logger + .t('[$runtimeType] requestWriteContract, chainId: $reqChainId'); final networkInfo = ReownAppKitModalNetworks.getNetworkById( reqChainId.split(':').first, @@ -1119,7 +1153,8 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } // - _logger.d('[$runtimeType] requestWriteContract, chainId: $reqChainId'); + _appKit.core.logger + .t('[$runtimeType] requestWriteContract, chainId: $reqChainId'); try { return await _appKit.requestWriteContract( @@ -1159,13 +1194,13 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } } // - _logger.d( + _appKit.core.logger.t( '[$runtimeType] request, chainId: $reqChainId, ' '${jsonEncode(request.toJson())}', ); try { if (_currentSession!.sessionService.isMagic) { - return await magicService.instance.request( + return await _magicService.request( chainId: reqChainId, request: request, ); @@ -1183,7 +1218,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { ); } catch (e) { if (_isUserRejectedError(e)) { - _logger.i('[$runtimeType] User declined request'); onModalError.broadcast(UserRejectedConnection()); if (request.method == MethodsConstants.walletSwitchEthChain || request.method == MethodsConstants.walletAddEthChain) { @@ -1209,7 +1243,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { await expirePreviousInactivePairings(); _unregisterListeners(); _status = ReownAppKitModalStatus.idle; - _logger.d('[$runtimeType] dispose'); } super.dispose(); } @@ -1281,8 +1314,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { if (wrongNamespace != null) { throw ReownAppKitModalException('Only eip155 blockains are supported'); } - - _logger.d('[$runtimeType] _requiredNamespaces $_requiredNamespaces'); } void _setOptionalNamespaces(Map? optionalNSpaces) { @@ -1319,8 +1350,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { 'Only ${CoreConstants.namespace} networks are supported', ); } - - _logger.d('[$runtimeType] _optionalNamespaces $_optionalNamespaces'); } /// Loads account balance and avatar. @@ -1335,15 +1364,23 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } // Get the chain balance. - _chainBalance = await blockchainService.instance.rpcRequest( - chainId: '${CoreConstants.namespace}:$_currentSelectedChainId', - request: SessionRequestParams( - method: 'eth_getBalance', - params: [_currentSession!.address!, 'latest'], - ), - ); - final tokenName = selectedChain?.currency ?? ''; - balanceNotifier.value = '$_chainBalance $tokenName'; + try { + _chainBalance = await blockchainService.instance.rpcRequest( + chainId: '${CoreConstants.namespace}:$_currentSelectedChainId', + request: SessionRequestParams( + method: 'eth_getBalance', + params: [_currentSession!.address!, 'latest'], + ), + ); + final tokenName = selectedChain?.currency ?? ''; + balanceNotifier.value = + '${CoreUtils.formatChainBalance(_chainBalance)} $tokenName'; + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] loadAccountData error: $e', + stackTrace: s, + ); + } // Get the avatar, each chainId is just a number in string form. try { @@ -1351,9 +1388,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _currentSession!.address!, ); _avatarUrl = blockchainId.avatar; - _logger.i('[$runtimeType] loadAccountData'); - } catch (e) { - _logger.e('[$runtimeType] loadAccountData $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] loadAccountData error: $e', + stackTrace: s, + ); } _notify(); } @@ -1367,7 +1406,8 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { } final currentChain = '${CoreConstants.namespace}:$_currentSelectedChainId'; final newChainId = '${CoreConstants.namespace}:${newChain.chainId}'; - _logger.i('[$runtimeType] requesting switch to chain $newChainId'); + _appKit.core.logger + .i('[$runtimeType] requesting switch to chain $newChainId'); try { await request( topic: _currentSession?.topic ?? '', @@ -1384,7 +1424,6 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { await _setSesionAndChainData(_currentSession!); return; } catch (e) { - _logger.i('[$runtimeType] requestSwitchToChain error $e'); // if request errors due to user rejection then set the previous chain if (_isUserRejectedError(e)) { // fallback to current chain if rejected by user @@ -1394,7 +1433,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { try { // Otherwise it meas chain has to be added. return await requestAddChain(newChain); - } catch (e) { + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] requestSwitchToChain error: $e', + stackTrace: s, + ); rethrow; } } @@ -1406,7 +1449,7 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { final topic = _currentSession?.topic ?? ''; final chainId = '${CoreConstants.namespace}:$_currentSelectedChainId'; final newChainId = '${CoreConstants.namespace}:${newChain.chainId}'; - _logger.i('[$runtimeType] requesting switch to add chain $newChainId'); + _appKit.core.logger.i('[$runtimeType] requesting add chain $newChainId'); try { await request( topic: topic, @@ -1420,8 +1463,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { _currentSelectedChainId = newChain.chainId; await _setSesionAndChainData(_currentSession!); return; - } catch (e) { - _logger.i('[$runtimeType] requestAddChain error $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] requestAddChain error: $e', + stackTrace: s, + ); await _setLocalEthChain(_currentSelectedChainId!); throw JsonRpcError(code: 5002, message: 'User rejected methods.'); } @@ -1490,11 +1536,11 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { void _registerListeners() { // Magic - magicService.instance.onMagicConnect.subscribe(_onMagicConnectEvent); - magicService.instance.onMagicLoginSuccess.subscribe(_onMagicLoginEvent); - magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); - magicService.instance.onMagicUpdate.subscribe(_onMagicSessionUpdateEvent); - magicService.instance.onMagicRpcRequest.subscribe(_onMagicRequest); + _magicService.onMagicConnect.subscribe(_onMagicConnectEvent); + _magicService.onMagicLoginSuccess.subscribe(_onMagicLoginEvent); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.onMagicUpdate.subscribe(_onMagicSessionUpdateEvent); + _magicService.onMagicRpcRequest.subscribe(_onMagicRequest); // // Coinbase coinbaseService.instance.onCoinbaseConnect.subscribe( @@ -1534,17 +1580,20 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { onSiweFinish: _oneSIWEFinish, )); } - } catch (e) { - _logger.e('[$runtimeType] _onNetworkChainRequireSIWE $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] _onNetworkChainRequireSIWE error: $e', + stackTrace: s, + ); } } void _unregisterListeners() { // Magic - magicService.instance.onMagicLoginSuccess.unsubscribe(_onMagicLoginEvent); - magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); - magicService.instance.onMagicUpdate.unsubscribe(_onMagicSessionUpdateEvent); - magicService.instance.onMagicRpcRequest.unsubscribe(_onMagicRequest); + _magicService.onMagicLoginSuccess.unsubscribe(_onMagicLoginEvent); + _magicService.onMagicError.unsubscribe(_onMagicErrorEvent); + _magicService.onMagicUpdate.unsubscribe(_onMagicSessionUpdateEvent); + _magicService.onMagicRpcRequest.unsubscribe(_onMagicRequest); // // Coinbase coinbaseService.instance.onCoinbaseConnect.unsubscribe( @@ -1587,13 +1636,19 @@ class ReownAppKitModal with ChangeNotifier implements IReownAppKitModal { extension _EmailConnectorExtension on ReownAppKitModal { // Login event should be treated like Connect event for regular wallets Future _onMagicLoginEvent(MagicLoginEvent? args) async { - final debugString = jsonEncode(args?.data?.toJson()); - _logger.i('[$runtimeType] _onMagicLoginEvent: $debugString'); if (args!.data != null) { final newChainId = _getStoredChainId('${args.data!.chainId}')!; _currentSelectedChainId = newChainId; // - final magicData = args.data?.copytWith(chainId: int.tryParse(newChainId)); + final email = args.data?.email ?? _currentSession?.toRawJson()['email']; + final userName = + args.data?.userName ?? _currentSession?.toRawJson()['userName']; + final magicData = args.data?.copytWith( + chainId: int.tryParse(newChainId), + email: email, + userName: userName, + ); + final session = ReownAppKitModalSession(magicData: magicData); await _setSesionAndChainData(session); onModalConnect.broadcast(ModalConnect(session)); @@ -1609,7 +1664,7 @@ extension _EmailConnectorExtension on ReownAppKitModal { } else { _disconnectOnClose = true; final theme = ReownAppKitModalTheme.maybeOf(_context!); - await magicService.instance.syncTheme(theme); + await _magicService.syncTheme(theme); widgetStack.instance.push(ApproveSIWEPage( onSiweFinish: _oneSIWEFinish, )); @@ -1623,31 +1678,36 @@ extension _EmailConnectorExtension on ReownAppKitModal { } Future _onMagicSessionUpdateEvent(MagicSessionEvent? args) async { - _logger.d('[$runtimeType] _onMagicUpdateEvent: $args'); if (args != null) { try { - final newEmail = args.email ?? _currentSession!.email; - final address = args.address ?? _currentSession!.address!; - final chainId = args.chainId?.toString() ?? _currentSession!.chainId; - _currentSelectedChainId = chainId; + final currentUsername = _currentSession?.userName; + final currentEmail = _currentSession?.email; + final newEmail = args.email ?? currentEmail ?? currentUsername; + final newUsername = args.userName ?? currentUsername; + final newProvider = args.provider ?? _currentSession?.socialProvider; + final newAddress = args.address ?? _currentSession!.address!; + final newChainId = args.chainId?.toString() ?? _currentSession!.chainId; + _currentSelectedChainId = newChainId; // - final session = _currentSession!.copyWith( - magicData: MagicData( - email: newEmail, - address: address, - chainId: int.parse(chainId), - peer: magicService.instance.metadata, - self: ConnectionMetadata( - metadata: _appKit.metadata, - publicKey: '', - ), - ), + final magicData = MagicData( + email: newEmail, + address: newAddress, + userName: newUsername, + provider: newProvider, + chainId: int.parse(newChainId), ); + final session = (_currentSession != null) + ? _currentSession!.copyWith( + magicData: magicData, + ) + : ReownAppKitModalSession( + magicData: magicData, + ); await _setSesionAndChainData(session); onModalUpdate.broadcast(ModalConnect(session)); } catch (e, s) { - _logger.d( - '[$runtimeType] _onMagicUpdateEvent: $e', + _appKit.core.logger.d( + '[$runtimeType] _onMagicUpdateEvent error: $e', stackTrace: s, ); } @@ -1655,7 +1715,7 @@ extension _EmailConnectorExtension on ReownAppKitModal { } Future _onMagicErrorEvent(MagicErrorEvent? args) async { - _logger.d('[$runtimeType] _onMagicErrorEvent ${args?.error}'); + _appKit.core.logger.d('[$runtimeType] _onMagicErrorEvent: $args'); final errorMessage = args?.error ?? 'Something went wrong'; if (!errorMessage.toLowerCase().contains('user denied')) { onModalError.broadcast(ModalError(errorMessage)); @@ -1664,7 +1724,6 @@ extension _EmailConnectorExtension on ReownAppKitModal { } void _onMagicRequest(MagicRequestEvent? args) { - _logger.d('[$runtimeType] _onMagicRequest ${args?.toString()}'); if (args?.result != null) { if (args!.result is JsonRpcError && widgetStack.instance.canPop()) { widgetStack.instance.pop(); @@ -1685,8 +1744,6 @@ extension _EmailConnectorExtension on ReownAppKitModal { extension _CoinbaseConnectorExtension on ReownAppKitModal { void _onCoinbaseConnectEvent(CoinbaseConnectEvent? args) async { - final debugString = jsonEncode(args?.data?.toJson()); - _logger.i('[$runtimeType] _onCoinbaseConnectEvent: $debugString'); if (args?.data != null) { final newChainId = _getStoredChainId('${args!.data!.chainId}')!; _currentSelectedChainId = newChainId; @@ -1709,7 +1766,6 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { } void _onCoinbaseSessionUpdateEvent(CoinbaseSessionEvent? args) async { - _logger.i('[$runtimeType] _onCoinbaseSessionUpdateEvent $args'); if (args != null) { try { final address = args.address ?? _currentSession!.address!; @@ -1735,8 +1791,8 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { await _setSesionAndChainData(session); onModalUpdate.broadcast(ModalConnect(session)); } catch (e, s) { - _logger.d( - '[$runtimeType] _onCoinbaseSessionUpdateEvent: $e', + _appKit.core.logger.d( + '[$runtimeType] _onCoinbaseSessionUpdateEvent error: $e', stackTrace: s, ); } @@ -1744,7 +1800,6 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { } void _onCoinbaseErrorEvent(CoinbaseErrorEvent? args) async { - _logger.d('[$runtimeType] _onCoinbaseErrorEvent ${args?.error}'); final errorMessage = args?.error ?? 'Something went wrong'; if (!errorMessage.toLowerCase().contains('user denied')) { onModalError.broadcast(ModalError(errorMessage)); @@ -1754,8 +1809,6 @@ extension _CoinbaseConnectorExtension on ReownAppKitModal { extension _AppKitModalExtension on ReownAppKitModal { void _onSessionAuthResponse(SessionAuthResponse? args) async { - final debugString = jsonEncode(args?.toJson()); - dev.log('[$runtimeType] _onSessionAuthResponse: $debugString'); if (args?.session != null) { // IF 1-CA SUPPORTED WE SHOULD CALL SIWECONGIF METHODS HERE final session = await _settleSession(args!.session!); @@ -1773,8 +1826,9 @@ extension _AppKitModalExtension on ReownAppKitModal { signature: cacao.s.s, clientId: clientId, ); - } catch (e) { - _logger.e('[$runtimeType] _onSessionAuthResponse $e'); + } catch (e, s) { + _appKit.core.logger + .e('[$runtimeType] onSessionAuthResponse $e', stackTrace: s); await disconnect(); return; } @@ -1792,8 +1846,6 @@ extension _AppKitModalExtension on ReownAppKitModal { } void _onSessionConnect(SessionConnect? args) async { - final debugString = jsonEncode(args?.session.toJson()); - dev.log('[$runtimeType] _onSessionConnect: $debugString'); final siweEnabled = siweService.instance!.enabled; if (_supportsOneClickAuth && siweEnabled) return; if (args != null) { @@ -1816,7 +1868,8 @@ extension _AppKitModalExtension on ReownAppKitModal { // HAS TO BE CALLED JUST ONCE ON CONNECTION Future _settleSession( - SessionData sessionData) async { + SessionData sessionData, + ) async { if (_currentSelectedChainId == null) { final chains = NamespaceUtils.getChainIdsFromNamespaces( namespaces: sessionData.namespaces, @@ -1851,9 +1904,11 @@ extension _AppKitModalExtension on ReownAppKitModal { StorageConstants.selectedChainId, {'chainId': _currentSelectedChainId!}, ); - debugPrint('_oneSIWEFinish {\'chainId\': $_currentSelectedChainId}'); - } catch (e) { - _logger.e('[$runtimeType] _setLocalEthChain error: $e'); + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] _oneSIWEFinish error: $e', + stackTrace: s, + ); } onModalUpdate.broadcast(ModalConnect(updatedSession)); closeModal(); @@ -1863,7 +1918,6 @@ extension _AppKitModalExtension on ReownAppKitModal { } void _onSessionEvent(SessionEvent? args) async { - _logger.i('[$runtimeType] session event $args'); onSessionEventEvent.broadcast(args); if (args?.name == EventsConstants.chainChanged) { _currentSelectedChainId = args?.data?.toString(); @@ -1873,13 +1927,17 @@ extension _AppKitModalExtension on ReownAppKitModal { if (siweService.instance!.signOutOnAccountChange) { await siweService.instance!.signOut(); } - } catch (_) {} + } catch (e, s) { + _appKit.core.logger.d( + '[$runtimeType] _onSessionEvent error: $e', + stackTrace: s, + ); + } } _notify(); } void _onSessionUpdate(SessionUpdate? args) async { - _logger.i('[$runtimeType] session update $args'); if (args != null) { final wcSessions = _appKit.sessions.getAll(); if (wcSessions.isEmpty) return; @@ -1897,17 +1955,15 @@ extension _AppKitModalExtension on ReownAppKitModal { } void _onSessionExpire(SessionExpire? args) { - _logger.i('[$runtimeType] session expire $args'); onSessionExpireEvent.broadcast(args); } void _onSessionDelete(SessionDelete? args) { - _logger.i('[$runtimeType] session delete $args'); _cleanSession(args: args); } void _onRelayClientConnect(EventArgs? args) { - _logger.i('[$runtimeType] relay client connected'); + _appKit.core.logger.i('[$runtimeType] relay client connected'); final service = _currentSession?.sessionService ?? ReownAppKitModalConnector.wc; if (service.isWC && _serviceInitialized) { @@ -1917,7 +1973,7 @@ extension _AppKitModalExtension on ReownAppKitModal { } void _onRelayClientDisconnect(EventArgs? args) { - _logger.i('[$runtimeType] relay client disconnected'); + _appKit.core.logger.i('[$runtimeType] relay client disconnected'); final service = _currentSession?.sessionService ?? ReownAppKitModalConnector.wc; if (service.isWC && _serviceInitialized) { @@ -1927,7 +1983,7 @@ extension _AppKitModalExtension on ReownAppKitModal { } void _onRelayClientError(ErrorEvent? args) { - _logger.i('[$runtimeType] relay client error: ${args?.error}'); + _appKit.core.logger.i('[$runtimeType] relay client error: ${args?.error}'); final service = _currentSession?.sessionService ?? ReownAppKitModalConnector.wc; if (service.isWC) { diff --git a/packages/reown_appkit/lib/modal/assets/dark/apple_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/apple_logo.svg new file mode 100644 index 00000000..b9c0a528 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/apple_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/discord_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/discord_logo.svg new file mode 100644 index 00000000..d3e8e7d1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/discord_logo.svg @@ -0,0 +1,32 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/facebook_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/facebook_logo.svg new file mode 100644 index 00000000..e100f2b2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/facebook_logo.svg @@ -0,0 +1,40 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/farcaster_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/farcaster_logo.svg new file mode 100644 index 00000000..6c82bf84 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/farcaster_logo.svg @@ -0,0 +1,43 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/github_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/github_logo.svg new file mode 100644 index 00000000..66bf7fc4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/github_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/google_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/google_logo.svg new file mode 100644 index 00000000..f2607cd4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/google_logo.svg @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/more_social_icon.svg b/packages/reown_appkit/lib/modal/assets/dark/more_social_icon.svg new file mode 100644 index 00000000..f4f3afd1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/more_social_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/dark/telegram_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/telegram_logo.svg new file mode 100644 index 00000000..c694fcdd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/telegram_logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/twitch_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/twitch_logo.svg new file mode 100644 index 00000000..706f0229 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/twitch_logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/dark/x_logo.svg b/packages/reown_appkit/lib/modal/assets/dark/x_logo.svg new file mode 100644 index 00000000..11ff04c3 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/dark/x_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/apple_logo.svg b/packages/reown_appkit/lib/modal/assets/light/apple_logo.svg new file mode 100644 index 00000000..b9c0a528 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/apple_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/discord_logo.svg b/packages/reown_appkit/lib/modal/assets/light/discord_logo.svg new file mode 100644 index 00000000..d3e8e7d1 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/discord_logo.svg @@ -0,0 +1,32 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/facebook_logo.svg b/packages/reown_appkit/lib/modal/assets/light/facebook_logo.svg new file mode 100644 index 00000000..e100f2b2 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/facebook_logo.svg @@ -0,0 +1,40 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/farcaster_logo.svg b/packages/reown_appkit/lib/modal/assets/light/farcaster_logo.svg new file mode 100644 index 00000000..6c82bf84 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/farcaster_logo.svg @@ -0,0 +1,43 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/github_logo.svg b/packages/reown_appkit/lib/modal/assets/light/github_logo.svg new file mode 100644 index 00000000..66bf7fc4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/github_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/google_logo.svg b/packages/reown_appkit/lib/modal/assets/light/google_logo.svg new file mode 100644 index 00000000..f2607cd4 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/google_logo.svg @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/more_social_icon.svg b/packages/reown_appkit/lib/modal/assets/light/more_social_icon.svg new file mode 100644 index 00000000..84118583 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/more_social_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/reown_appkit/lib/modal/assets/light/telegram_logo.svg b/packages/reown_appkit/lib/modal/assets/light/telegram_logo.svg new file mode 100644 index 00000000..c694fcdd --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/telegram_logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/twitch_logo.svg b/packages/reown_appkit/lib/modal/assets/light/twitch_logo.svg new file mode 100644 index 00000000..706f0229 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/twitch_logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/light/x_logo.svg b/packages/reown_appkit/lib/modal/assets/light/x_logo.svg new file mode 100644 index 00000000..11ff04c3 --- /dev/null +++ b/packages/reown_appkit/lib/modal/assets/light/x_logo.svg @@ -0,0 +1,28 @@ + + + +Created with Fabric.js 5.2.4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/reown_appkit/lib/modal/assets/png/2.0x/farcaster.png b/packages/reown_appkit/lib/modal/assets/png/2.0x/farcaster.png new file mode 100644 index 00000000..f178425c Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/2.0x/farcaster.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/3.0x/farcaster.png b/packages/reown_appkit/lib/modal/assets/png/3.0x/farcaster.png new file mode 100644 index 00000000..dd5b0ed6 Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/3.0x/farcaster.png differ diff --git a/packages/reown_appkit/lib/modal/assets/png/farcaster.png b/packages/reown_appkit/lib/modal/assets/png/farcaster.png new file mode 100644 index 00000000..368c362b Binary files /dev/null and b/packages/reown_appkit/lib/modal/assets/png/farcaster.png differ diff --git a/packages/reown_appkit/lib/modal/constants/key_constants.dart b/packages/reown_appkit/lib/modal/constants/key_constants.dart index 1ecbb7b6..697e3ee0 100644 --- a/packages/reown_appkit/lib/modal/constants/key_constants.dart +++ b/packages/reown_appkit/lib/modal/constants/key_constants.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'package:flutter/material.dart'; class KeyConstants { @@ -11,7 +13,9 @@ class KeyConstants { static const Key upgradeWalletPage = Key('upgradeWalletPage'); static const Key helpPageKey = Key('helpPageKey'); static const Key qrCodePageKey = Key('qrCodePageKey'); + static const Key farcasterQrCodePageKey = Key('farcasterQrCodePageKey'); static const Key walletListShortPageKey = Key('walletListShortPageKey'); + static const Key allSocialLoginPageKey = Key('allSocialLoginPageKey'); static const Key walletListLongPageKey = Key('walletListLongPageKey'); static const Key connectWalletPageKey = Key('connectWalletPageKey'); static const Key connecNetworkPageKey = Key('connecNetworkPageKey'); @@ -21,6 +25,7 @@ class KeyConstants { static const Key approveTransactionPage = Key('approveTransactionPage'); static const Key confirmEmailPage = Key('confirmEmailPage'); static const Key approveSiwePageKey = Key('approveSiwePageKey'); + static const Key socialLoginPage = Key('socialLoginPage'); // Buttons static const Key helpButtonKey = Key('helpButtonKey'); diff --git a/packages/reown_appkit/lib/modal/constants/string_constants.dart b/packages/reown_appkit/lib/modal/constants/string_constants.dart index b129e1a2..aee69eb4 100644 --- a/packages/reown_appkit/lib/modal/constants/string_constants.dart +++ b/packages/reown_appkit/lib/modal/constants/string_constants.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'package:reown_appkit/reown_appkit.dart'; import 'package:reown_sign/version.dart' as reown_sign; @@ -46,9 +48,10 @@ class UrlConstants { static const exploreWallets = 'https://explorer.walletconnect.com/?type=wallet'; // - static const secureService = - 'https://secure-mobile.walletconnect.com/mobile-sdk'; - static const secureDashboard = 'https://secure.walletconnect.com/dashboard'; + static const secureOrigin1 = 'secure-mobile.walletconnect.com'; + static const secureOrigin2 = 'secure.walletconnect.com'; + static const secureService = 'https://$secureOrigin1/mobile-sdk'; + static const secureDashboard = 'https://$secureOrigin2/dashboard'; // static const learnMoreUrl = 'https://ethereum.org/en/developers/docs/networks'; diff --git a/packages/reown_appkit/lib/modal/constants/style_constants.dart b/packages/reown_appkit/lib/modal/constants/style_constants.dart index d2adf3bf..da3af949 100644 --- a/packages/reown_appkit/lib/modal/constants/style_constants.dart +++ b/packages/reown_appkit/lib/modal/constants/style_constants.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + const kListItemHeight = 56.0; const kGridItemWidth = 76.0; diff --git a/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart b/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart index 63725458..63e16027 100644 --- a/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart +++ b/packages/reown_appkit/lib/modal/i_appkit_modal_impl.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:reown_appkit/reown_appkit.dart'; @@ -23,6 +25,8 @@ abstract class IReownAppKitModal with ChangeNotifier { bool get hasNamespaces; + FeaturesConfig get featuresConfig; + /// The object that manages sessions, authentication, events, and requests for WalletConnect. IReownAppKit? get appKit; @@ -44,8 +48,10 @@ abstract class IReownAppKitModal with ChangeNotifier { String? get avatarUrl; /// Returns the balance of the currently connected wallet on the selected chain. + @Deprecated('Use balanceNotifier') String get chainBalance; + /// Returns the balance of the currently connected wallet on the selected chain. ValueNotifier get balanceNotifier; /// The currently selected chain. @@ -57,10 +63,7 @@ abstract class IReownAppKitModal with ChangeNotifier { /// Sets up the explorer and appKit if they already been initialized. Future init(); - // @Deprecated( - // 'Add context param to ReownAppKitModal and use openNetworksView() instead') - // Future openNetworks(BuildContext context); - + /// Opens modal on Network Selection Screen Future openNetworksView(); /// Opens the modal with the provided [startWidget] (if any). @@ -74,7 +77,7 @@ abstract class IReownAppKitModal with ChangeNotifier { /// Sets the [selectedWallet] to be connected void selectWallet(ReownAppKitModalWalletInfo? walletInfo); - /// Sets the [selectedChain] and gets the [chainBalance]. + /// Sets the [selectedChain] /// If the wallet is already connected, it will request the chain to be changed and will update the session with the new chain. /// If [chainInfo] is null this will disconnect the wallet. Future selectChain( @@ -109,6 +112,7 @@ abstract class IReownAppKitModal with ChangeNotifier { /// List of approved events by connected wallet List? getApprovedEvents(); + /// Loads/Refresh account balance and identity Future loadAccountData(); /// Disconnects the session and pairing, if any. @@ -145,6 +149,8 @@ abstract class IReownAppKitModal with ChangeNotifier { Future requestSwitchToChain(ReownAppKitModalNetworkInfo newChain); Future requestAddChain(ReownAppKitModalNetworkInfo newChain); + Future dispatchEnvelope(String url); + /// Closes the modal. void closeModal({bool disconnectSession = false}); diff --git a/packages/reown_appkit/lib/modal/models/grid_item.dart b/packages/reown_appkit/lib/modal/models/grid_item.dart index 92366fce..f4c59757 100644 --- a/packages/reown_appkit/lib/modal/models/grid_item.dart +++ b/packages/reown_appkit/lib/modal/models/grid_item.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + class GridItem { final String image; final String id; diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart index 338d13ae..ce716f2f 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_events.dart @@ -1,5 +1,6 @@ import 'package:reown_appkit/reown_appkit.dart'; +/// Event fired when connection is done class ModalConnect extends EventArgs { final ReownAppKitModalSession session; ModalConnect(this.session); @@ -10,6 +11,7 @@ class ModalConnect extends EventArgs { } } +/// Event fired when network is changed through the modal class ModalNetworkChange extends EventArgs { final String chainId; ModalNetworkChange({required this.chainId}); @@ -20,6 +22,7 @@ class ModalNetworkChange extends EventArgs { } } +/// Event fired when disconnect happens, either from the wallet of the modal class ModalDisconnect extends EventArgs { final String? topic; final int? id; @@ -31,6 +34,7 @@ class ModalDisconnect extends EventArgs { } } +/// Event fired every time an error occurs class ModalError extends EventArgs { final String message; ModalError(this.message); @@ -41,14 +45,17 @@ class ModalError extends EventArgs { } } +/// Event fired when trying to opening a wallet that is not installed class WalletNotInstalled extends ModalError { WalletNotInstalled() : super('Wallet app not installed'); } +/// Error opening wallet class ErrorOpeningWallet extends ModalError { ErrorOpeningWallet() : super('Unable to open Wallet app'); } +/// Event fired when user rejects connection in the wallet class UserRejectedConnection extends ModalError { UserRejectedConnection() : super('User rejected connection'); } diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_features_config.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_features_config.dart new file mode 100644 index 00000000..47c0292e --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_features_config.dart @@ -0,0 +1,14 @@ +import 'package:reown_appkit/modal/models/public/appkit_social_options.dart'; + +/// Object to pass to [featuresConfig:] parameter of ReownAppKitModal to enable or disable these extra features +class FeaturesConfig { + final bool email; + final List socials; + final bool showMainWallets; + + FeaturesConfig({ + this.email = true, + this.socials = const [], + this.showMainWallets = true, + }); +} diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart index 8698749e..0f3b11bb 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_models.dart @@ -1,6 +1,8 @@ -export 'appkit_network_info.dart'; -export 'appkit_wallet_info.dart'; -export 'appkit_siwe_config.dart'; -export 'appkit_modal_session.dart'; export 'appkit_modal_events.dart'; export 'appkit_modal_exceptions.dart'; +export 'appkit_modal_features_config.dart'; +export 'appkit_modal_session.dart'; +export 'appkit_network_info.dart'; +export 'appkit_siwe_config.dart'; +export 'appkit_social_options.dart'; +export 'appkit_wallet_info.dart'; diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart b/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart index cd94285d..1cf2336a 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_modal_session.dart @@ -1,7 +1,8 @@ +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/string_constants.dart'; import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service.dart'; import 'package:reown_appkit/modal/services/coinbase_service/models/coinbase_data.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; import 'package:reown_appkit/reown_appkit.dart'; @@ -18,6 +19,7 @@ enum ReownAppKitModalConnector { bool get noSession => this == ReownAppKitModalConnector.none; } +/// Session object of the modal when connected class ReownAppKitModalSession { SessionData? _sessionData; CoinbaseData? _coinbaseData; @@ -68,6 +70,7 @@ class ReownAppKitModalSession { ); } + /// Indicates the connected service ReownAppKitModalConnector get sessionService { if (_sessionData != null) { return ReownAppKitModalConnector.wc; @@ -76,6 +79,7 @@ class ReownAppKitModalSession { return ReownAppKitModalConnector.coinbase; } if (_magicData != null) { + // TODO rename to ReownAppKitModalConnector.socials return ReownAppKitModalConnector.magic; } @@ -89,6 +93,9 @@ class ReownAppKitModalSession { if (sessionService.isCoinbase) { return true; } + if (sessionService.isMagic) { + return true; + } final nsMethods = getApprovedMethods() ?? []; final supportsAddChain = nsMethods.contains( @@ -97,6 +104,7 @@ class ReownAppKitModalSession { return supportsAddChain; } + /// Get the approved methods by the connected peer List? getApprovedMethods() { if (sessionService.noSession) { return null; @@ -105,7 +113,7 @@ class ReownAppKitModalSession { return CoinbaseService.supportedMethods; } if (sessionService.isMagic) { - return MagicService.supportedMethods; + return GetIt.I().supportedMethods; } final sessionNamespaces = _sessionData!.namespaces; @@ -114,6 +122,7 @@ class ReownAppKitModalSession { return methodsList ?? []; } + /// Get the approved events by the connected peer List? getApprovedEvents() { if (sessionService.noSession) { return null; @@ -131,6 +140,7 @@ class ReownAppKitModalSession { return eventsList ?? []; } + /// Get the approved chains by the connected peer List? getApprovedChains() { if (sessionService.noSession) { return null; @@ -145,6 +155,7 @@ class ReownAppKitModalSession { return approvedChains; } + /// Get the approved accounts by the connected peer List? getAccounts() { if (sessionService.noSession) { return null; @@ -170,21 +181,25 @@ class ReownAppKitModalSession { // toJson() would convert ReownAppKitModalSession to a SessionData kind of map // no matter if Coinbase Wallet or Email Wallet is connected - Map toJson() => { - if (topic != null) 'topic': topic, - if (pairingTopic != null) 'pairingTopic': pairingTopic, - if (relay != null) 'relay': relay, - if (expiry != null) 'expiry': expiry, - if (acknowledged != null) 'acknowledged': acknowledged, - if (controller != null) 'controller': controller, - 'namespaces': _namespaces(), - if (requiredNamespaces != null) - 'requiredNamespaces': requiredNamespaces, - if (optionalNamespaces != null) - 'optionalNamespaces': optionalNamespaces, - 'self': self?.toJson(), - 'peer': peer?.toJson(), - }; + Map toJson() { + final sessionData = SessionData( + topic: topic ?? '', + pairingTopic: pairingTopic ?? '', + relay: relay ?? Relay(ReownConstants.RELAYER_DEFAULT_PROTOCOL), + expiry: expiry ?? 0, + acknowledged: acknowledged ?? false, + controller: controller ?? '', + namespaces: _namespaces() ?? {}, + self: self!, + peer: peer!, + requiredNamespaces: _sessionData?.requiredNamespaces, + optionalNamespaces: _sessionData?.optionalNamespaces, + sessionProperties: _sessionData?.sessionProperties, + authentication: _sessionData?.authentication, + transportType: _sessionData?.transportType ?? TransportType.relay, + ); + return sessionData.toJson(); + } } extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { @@ -195,18 +210,22 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { bool? get acknowledged => _sessionData?.acknowledged; String? get controller => _sessionData?.controller; Map? get namespaces => _sessionData?.namespaces; - Map? get requiredNamespaces => - _sessionData?.requiredNamespaces; - Map? get optionalNamespaces => - _sessionData?.optionalNamespaces; - Map? get sessionProperties => _sessionData?.sessionProperties; ConnectionMetadata? get self { if (sessionService.isCoinbase) { return _coinbaseData?.self; } if (sessionService.isMagic) { - return _magicData?.self; + return _magicData?.self ?? + ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata( + name: 'Email Wallet', + description: '', + url: '', + icons: [], + ), + ); } return _sessionData?.self; } @@ -216,7 +235,16 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { return _coinbaseData?.peer; } if (sessionService.isMagic) { - return _magicData?.peer; + return _magicData?.peer ?? + ConnectionMetadata( + publicKey: '', + metadata: PairingMetadata( + name: 'Email Wallet', + description: '', + url: '', + icons: [], + ), + ); } return _sessionData?.peer; } @@ -224,6 +252,10 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { // String get email => _magicData?.email ?? ''; + String get userName => _magicData?.userName ?? ''; + + AppKitSocialOption? get socialProvider => _magicData?.provider; + // String? get address { if (sessionService.noSession) { @@ -265,9 +297,6 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { if (sessionService.isCoinbase) { return CoinbaseService.defaultWalletData.listing.name; } - if (sessionService.isMagic) { - return MagicService.defaultWalletData.listing.name; - } if (sessionService.isWC) { return peer?.metadata.name; } @@ -298,7 +327,7 @@ extension ReownAppKitModalSessionExtension on ReownAppKitModalSession { CoreConstants.namespace: Namespace( chains: ['${CoreConstants.namespace}:$chainId'], accounts: ['${CoreConstants.namespace}:$chainId:$address'], - methods: [...MagicService.supportedMethods], + methods: [...GetIt.I().supportedMethods], events: [], ), }; diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart index 8ea32e19..2fc42488 100644 --- a/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart +++ b/packages/reown_appkit/lib/modal/models/public/appkit_siwe_config.dart @@ -5,6 +5,7 @@ import 'package:reown_appkit/reown_appkit.dart'; part 'appkit_siwe_config.g.dart'; part 'appkit_siwe_config.freezed.dart'; +/// Object to pass to [siweConfig:] parameter of ReownAppKitModal to enable or disable One-Click Auth + SIWE class SIWEConfig { final Future Function() getNonce; final Future Function() getMessageParams; diff --git a/packages/reown_appkit/lib/modal/models/public/appkit_social_options.dart b/packages/reown_appkit/lib/modal/models/public/appkit_social_options.dart new file mode 100644 index 00000000..d6ad2a2c --- /dev/null +++ b/packages/reown_appkit/lib/modal/models/public/appkit_social_options.dart @@ -0,0 +1,17 @@ +enum AppKitSocialOption { + X, + Apple, + Discord, + Farcaster; + // GitHub, + // Facebook, + // Google, + // Twitch, + // Telegram, + + factory AppKitSocialOption.fromString(String value) { + return AppKitSocialOption.values.firstWhere( + (e) => e.name.toLowerCase() == value.toLowerCase(), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/about_networks.dart b/packages/reown_appkit/lib/modal/pages/about_networks.dart index 9205d857..952645e8 100644 --- a/packages/reown_appkit/lib/modal/pages/about_networks.dart +++ b/packages/reown_appkit/lib/modal/pages/about_networks.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:reown_appkit/reown_appkit.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:reown_appkit/modal/constants/string_constants.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; @@ -46,10 +45,7 @@ class AboutNetworks extends StatelessWidget { ), const SizedBox(height: 8), SimpleIconButton( - onTap: () => launchUrlString( - UrlConstants.learnMoreUrl, - mode: LaunchMode.externalApplication, - ), + onTap: () => ReownCoreUtils.openURL(UrlConstants.learnMoreUrl), rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', title: 'Learn more', size: BaseButtonSize.small, diff --git a/packages/reown_appkit/lib/modal/pages/account_page.dart b/packages/reown_appkit/lib/modal/pages/account_page.dart index 00f77866..02475072 100644 --- a/packages/reown_appkit/lib/modal/pages/account_page.dart +++ b/packages/reown_appkit/lib/modal/pages/account_page.dart @@ -1,4 +1,6 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; import 'package:reown_appkit/modal/constants/style_constants.dart'; @@ -7,6 +9,7 @@ import 'package:reown_appkit/modal/pages/upgrade_wallet_page.dart'; import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; import 'package:reown_appkit/modal/widgets/circular_loader.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; @@ -88,7 +91,6 @@ class _DefaultAccountView extends StatelessWidget { Widget build(BuildContext context) { final themeData = ReownAppKitModalTheme.getDataOf(context); final themeColors = ReownAppKitModalTheme.colorsOf(context); - final radiuses = ReownAppKitModalTheme.radiusesOf(context); final isEmailLogin = _service.session?.sessionService.isMagic ?? false; return Column( mainAxisSize: MainAxisSize.min, @@ -121,59 +123,11 @@ class _DefaultAccountView extends StatelessWidget { const SizedBox.square(dimension: kPadding12), Visibility( visible: isEmailLogin, - child: Column( - children: [ - const SizedBox.square(dimension: kPadding8), - AccountListItem( - padding: const EdgeInsets.symmetric( - horizontal: kPadding8, - vertical: kPadding12, - ), - iconWidget: Padding( - padding: const EdgeInsets.all(4.0), - child: RoundedIcon( - borderRadius: radiuses.isSquare() - ? 0.0 - : radiuses.isCircular() - ? 40.0 - : 8.0, - size: 40.0, - assetPath: 'lib/modal/assets/icons/regular/wallet.svg', - assetColor: themeColors.accent100, - circleColor: themeColors.accenGlass010, - borderColor: themeColors.accenGlass010, - ), - ), - title: 'Upgrade your wallet', - subtitle: 'Transition to a self-custodial wallet', - hightlighted: true, - flexible: true, - titleStyle: themeData.textStyles.paragraph500.copyWith( - color: themeColors.foreground100, - ), - onTap: () => widgetStack.instance.push(UpgradeWalletPage()), - ), - ], - ), + child: _UpgradeWalletButton(), ), Visibility( visible: isEmailLogin, - child: Column( - children: [ - const SizedBox.square(dimension: kPadding8), - AccountListItem( - iconPath: 'lib/modal/assets/icons/mail.svg', - iconColor: themeColors.foreground100, - title: _service.session?.email ?? '', - titleStyle: themeData.textStyles.paragraph500.copyWith( - color: themeColors.foreground100, - ), - onTap: () { - widgetStack.instance.push(EditEmailPage()); - }, - ), - ], - ), + child: _EmailLoginButton(), ), const SizedBox.square(dimension: kPadding8), _SelectNetworkButton(), @@ -201,6 +155,103 @@ class _DefaultAccountView extends StatelessWidget { } } +class _UpgradeWalletButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + return Column( + children: [ + const SizedBox.square(dimension: kPadding8), + AccountListItem( + padding: const EdgeInsets.symmetric( + horizontal: kPadding8, + vertical: kPadding12, + ), + iconWidget: Padding( + padding: const EdgeInsets.all(4.0), + child: RoundedIcon( + borderRadius: radiuses.isSquare() + ? 0.0 + : radiuses.isCircular() + ? 40.0 + : 8.0, + size: 40.0, + assetPath: 'lib/modal/assets/icons/regular/wallet.svg', + assetColor: themeColors.accent100, + circleColor: themeColors.accenGlass010, + borderColor: themeColors.accenGlass010, + ), + ), + title: 'Upgrade your wallet', + subtitle: 'Transition to a self-custodial wallet', + hightlighted: true, + flexible: true, + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: () => widgetStack.instance.push(UpgradeWalletPage()), + ), + ], + ); + } +} + +class _EmailLoginButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final service = ModalProvider.of(context).instance; + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final provider = AppKitSocialOption.values.firstWhereOrNull( + (e) => e.name == service.session!.peer?.metadata.name, + ); + final title = service.session!.email.isNotEmpty + ? service.session!.email + : service.session!.userName; + return Column( + children: [ + const SizedBox.square(dimension: kPadding8), + AccountListItem( + iconWidget: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: provider == null + ? RoundedIcon( + assetPath: 'lib/modal/assets/icons/mail.svg', + assetColor: themeColors.foreground100, + borderRadius: radiuses.isSquare() ? 0.0 : null, + ) + : ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(34), + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + '${provider.name.toLowerCase()}_logo.svg', + ), + package: 'reown_appkit', + height: 34, + width: 34, + ), + ), + ), + title: title, + titleStyle: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + onTap: provider == null + ? () => widgetStack.instance.push(EditEmailPage()) + : null, + trailing: provider != null ? const SizedBox.shrink() : null, + ), + ], + ); + } +} + class _SelectNetworkButton extends StatelessWidget { @override Widget build(BuildContext context) { @@ -230,12 +281,10 @@ class _SelectNetworkButton extends StatelessWidget { titleStyle: themeData.textStyles.paragraph500.copyWith( color: themeColors.foreground100, ), - onTap: () { - widgetStack.instance.push( - ReownAppKitModalSelectNetworkPage(), - event: ClickNetworksEvent(), - ); - }, + onTap: () => widgetStack.instance.push( + ReownAppKitModalSelectNetworkPage(), + event: ClickNetworksEvent(), + ), ); } } diff --git a/packages/reown_appkit/lib/modal/pages/all_social_logins.dart b/packages/reown_appkit/lib/modal/pages/all_social_logins.dart new file mode 100644 index 00000000..a7f8c95f --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/all_social_logins.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/widgets/buttons/social_login_button.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AllSocialLoginsPage extends StatefulWidget { + const AllSocialLoginsPage({ + required this.onSelect, + }) : super(key: KeyConstants.allSocialLoginPageKey); + final Function(AppKitSocialOption) onSelect; + + @override + State createState() => + _AppKitModalMainWalletsPageState(); +} + +class _AppKitModalMainWalletsPageState extends State { + @override + Widget build(BuildContext context) { + final listItems = GetIt.I() + .socials + .map((item) => SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${item.name.toLowerCase()}_logo.svg', + ), + textAlign: TextAlign.left, + onTap: () => widget.onSelect(item), + title: item.name, + )) + .toList(); + return ModalNavbar( + title: 'All socials', + safeAreaLeft: true, + safeAreaRight: true, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: kPadding12), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ..._buttonsWithDivider(listItems), + ], + ), + ), + ), + ); + } + + List _buttonsWithDivider(List widgets) { + List spacedWidgets = []; + for (int i = 0; i < widgets.length; i++) { + spacedWidgets.add(widgets[i]); + if (i < widgets.length - 1) { + spacedWidgets.add( + const SizedBox.square(dimension: kListViewSeparatorHeight), + ); + } + } + return spacedWidgets; + } +} diff --git a/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart b/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart index 52d01d9a..f2493532 100644 --- a/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart +++ b/packages/reown_appkit/lib/modal/pages/approve_magic_request_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; @@ -15,9 +16,6 @@ class ApproveTransactionPage extends StatefulWidget { class _ApproveTransactionPageState extends State { @override Widget build(BuildContext context) { - magicService.instance.controller.runJavaScript( - 'document.body.style.zoom = "1%"', - ); return ModalNavbar( title: 'Approve Transaction', noClose: true, @@ -27,7 +25,7 @@ class _ApproveTransactionPageState extends State { constraints: BoxConstraints( maxHeight: ResponsiveData.maxHeightOf(context), ), - child: magicService.instance.webview, + child: GetIt.I().webview, ), ); } diff --git a/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart b/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart index f76f71bc..e9de5782 100644 --- a/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart +++ b/packages/reown_appkit/lib/modal/pages/confirm_email_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; - import 'package:reown_appkit/modal/constants/style_constants.dart'; import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; @@ -22,16 +22,18 @@ class ConfirmEmailPage extends StatefulWidget { } class _ConfirmEmailPageState extends State { + IMagicService get _magicService => GetIt.I(); + @override void initState() { super.initState(); - magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); } @override void dispose() { - magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); - magicService.instance.step.value = EmailLoginStep.idle; + _magicService.onMagicError.unsubscribe(_onMagicErrorEvent); + _magicService.step.value = EmailLoginStep.idle; super.dispose(); } @@ -48,8 +50,8 @@ class _ConfirmEmailPageState extends State { } void _goBack() { - magicService.instance.step.value = EmailLoginStep.idle; - magicService.instance.setEmail(''); + _magicService.step.value = EmailLoginStep.idle; + _magicService.setEmail(''); FocusManager.instance.primaryFocus?.unfocus(); widgetStack.instance.pop(); } @@ -57,7 +59,7 @@ class _ConfirmEmailPageState extends State { @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: magicService.instance.step, + valueListenable: _magicService.step, builder: (context, action, _) { final title = (action == EmailLoginStep.verifyDevice) ? 'Register device' @@ -74,9 +76,9 @@ class _ConfirmEmailPageState extends State { } if (action == EmailLoginStep.verifyOtp) { return VerifyOtpView( - currentEmail: magicService.instance.email.value, - resendEmail: magicService.instance.connectEmail, - verifyOtp: magicService.instance.connectOtp, + currentEmail: _magicService.email.value, + resendEmail: _magicService.connectEmail, + verifyOtp: _magicService.connectOtp, ); } return ContentLoading(viewHeight: 200.0); @@ -95,6 +97,7 @@ class _VerifyDeviceView extends StatefulWidget { class __VerifyDeviceViewState extends State<_VerifyDeviceView> { late DateTime _resendEnabledAt; + IMagicService get _magicService => GetIt.I(); @override void initState() { @@ -110,8 +113,8 @@ class __VerifyDeviceViewState extends State<_VerifyDeviceView> { text: 'Try again after ${diff.abs()} seconds', )); } else { - final email = magicService.instance.email.value; - await magicService.instance.connectEmail(value: email); + final email = _magicService.email.value; + await _magicService.connectEmail(value: email); _resendEnabledAt = DateTime.now().add(Duration(seconds: 30)); toastService.instance.show(ToastMessage( type: ToastType.success, @@ -155,7 +158,7 @@ class __VerifyDeviceViewState extends State<_VerifyDeviceView> { ), ), Text( - magicService.instance.email.value, + _magicService.email.value, textAlign: TextAlign.center, style: textStyles.paragraph500.copyWith( color: themeColors.foreground100, diff --git a/packages/reown_appkit/lib/modal/pages/edit_email_page.dart b/packages/reown_appkit/lib/modal/pages/edit_email_page.dart index 29e7e4c5..8d54bea4 100644 --- a/packages/reown_appkit/lib/modal/pages/edit_email_page.dart +++ b/packages/reown_appkit/lib/modal/pages/edit_email_page.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/modal/constants/style_constants.dart'; import 'package:reown_appkit/modal/utils/core_utils.dart'; @@ -25,15 +26,16 @@ class EditEmailPage extends StatefulWidget { class _EditEmailPageState extends State { late final String _currentEmailValue; + IMagicService get _magicService => GetIt.I(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { - magicService.instance.onMagicError.subscribe(_onMagicErrorEvent); - _currentEmailValue = magicService.instance.email.value; - if (!magicService.instance.isConnected.value) { - magicService.instance.connectEmail(value: _currentEmailValue); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _currentEmailValue = _magicService.email.value; + if (!_magicService.isConnected.value) { + _magicService.connectEmail(value: _currentEmailValue); widgetStack.instance.popAllAndPush(ConfirmEmailPage()); } }); @@ -41,7 +43,7 @@ class _EditEmailPageState extends State { @override void dispose() { - magicService.instance.onMagicError.unsubscribe(_onMagicErrorEvent); + _magicService.onMagicError.unsubscribe(_onMagicErrorEvent); super.dispose(); } @@ -55,16 +57,16 @@ class _EditEmailPageState extends State { void _goBack() { FocusManager.instance.primaryFocus?.unfocus(); - magicService.instance.setEmail(_currentEmailValue); - magicService.instance.setNewEmail(''); + _magicService.setEmail(_currentEmailValue); + _magicService.setNewEmail(''); widgetStack.instance.pop(); - magicService.instance.step.value = EmailLoginStep.idle; + _magicService.step.value = EmailLoginStep.idle; } @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: magicService.instance.step, + valueListenable: _magicService.step, builder: (context, action, _) { String title = 'Edit Email'; if (action == EmailLoginStep.verifyOtp) { @@ -87,12 +89,12 @@ class _EditEmailPageState extends State { action == EmailLoginStep.verifyOtp2) { return VerifyOtpView( currentEmail: (action == EmailLoginStep.verifyOtp2) - ? magicService.instance.newEmail.value - : magicService.instance.email.value, + ? _magicService.newEmail.value + : _magicService.email.value, resendEmail: _resendEmail, verifyOtp: (action == EmailLoginStep.verifyOtp2) - ? magicService.instance.updateEmailSecondaryOtp - : magicService.instance.updateEmailPrimaryOtp, + ? _magicService.updateEmailSecondaryOtp + : _magicService.updateEmailPrimaryOtp, ); } return _EditEmailView(); @@ -104,8 +106,8 @@ class _EditEmailPageState extends State { } Future _resendEmail({String? value}) async { - final email = magicService.instance.newEmail.value; - magicService.instance.updateEmail(value: email); + final email = _magicService.newEmail.value; + _magicService.updateEmail(value: email); } } @@ -115,6 +117,7 @@ class _EditEmailView extends StatefulWidget { } class __EditEmailViewState extends State<_EditEmailView> { + IMagicService get _magicService => GetIt.I(); String _newEmailValue = ''; late final String _currentEmailValue; bool _isValidEmail = false; @@ -122,12 +125,12 @@ class __EditEmailViewState extends State<_EditEmailView> { @override void initState() { super.initState(); - _currentEmailValue = magicService.instance.email.value; + _currentEmailValue = _magicService.email.value; _newEmailValue = _currentEmailValue; } void _onValueChange(String value) { - magicService.instance.setNewEmail(value); + _magicService.setNewEmail(value); _newEmailValue = value; final valid = CoreUtils.isValidEmail(value); setState(() { @@ -137,14 +140,14 @@ class __EditEmailViewState extends State<_EditEmailView> { void _onSubmittedEmail(String value) { FocusManager.instance.primaryFocus?.unfocus(); - // magicService.instance.setNewEmail(value); - magicService.instance.updateEmail(value: value); + // _magicService.setNewEmail(value); + _magicService.updateEmail(value: value); } void _goBack() { FocusManager.instance.primaryFocus?.unfocus(); - magicService.instance.setEmail(_currentEmailValue); - magicService.instance.setNewEmail(''); + _magicService.setEmail(_currentEmailValue); + _magicService.setNewEmail(''); widgetStack.instance.pop(); } diff --git a/packages/reown_appkit/lib/modal/pages/farcaster_qrcode_page.dart b/packages/reown_appkit/lib/modal/pages/farcaster_qrcode_page.dart new file mode 100644 index 00000000..1e20b52b --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/farcaster_qrcode_page.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/pages/social_login_page.dart'; +import 'package:reown_appkit/modal/widgets/buttons/primary_button.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/widgets/qr_code_view.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:shimmer/shimmer.dart'; + +class FarcasterQRCodePage extends StatefulWidget { + const FarcasterQRCodePage({ + required this.farcasterUri, + required this.farcasterCompleter, + }) : super(key: KeyConstants.farcasterQrCodePageKey); + final String farcasterUri; + final Future? farcasterCompleter; + + @override + State createState() => _FarcasterQRCodePageState(); +} + +class _FarcasterQRCodePageState extends State { + IReownAppKitModal? _service; + Widget? _qrQodeWidget; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + _service = ModalProvider.of(context).instance; + _service?.onModalError.subscribe(_onError); + _qrQodeWidget = QRCodeView( + uri: widget.farcasterUri, + logoPath: 'lib/modal/assets/png/farcaster.png', + ); + setState(() {}); + widget.farcasterCompleter?.then((value) { + widgetStack.instance.push( + SocialLoginPage( + socialOption: AppKitSocialOption.Farcaster, + farcasterCompleter: widget.farcasterCompleter, + ), + replace: true, + ); + }); + }); + } + + void _onError(ModalError? args) { + final event = args ?? ModalError('An error occurred'); + toastService.instance.show( + ToastMessage( + type: ToastType.error, + text: event.message, + ), + ); + } + + @override + void dispose() async { + _service?.onModalError.unsubscribe(_onError); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + + return ModalNavbar( + title: 'Farcaster', + body: SingleChildScrollView( + scrollDirection: isPortrait ? Axis.vertical : Axis.horizontal, + child: Flex( + direction: isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + Padding( + padding: EdgeInsets.all(20.0), + child: _qrQodeWidget ?? + AspectRatio( + aspectRatio: 1.0, + child: Shimmer.fromColors( + baseColor: themeColors.grayGlass100, + highlightColor: themeColors.grayGlass025, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radiuses.radiusL), + color: themeColors.grayGlass010, + ), + ), + ), + ), + ), + Container( + constraints: BoxConstraints( + maxWidth: isPortrait + ? ResponsiveData.maxWidthOf(context) + : (ResponsiveData.maxHeightOf(context) - + kNavbarHeight - + 32.0), + ), + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Scan this QR code with your phone or', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: kPadding12), + child: PrimaryButton( + title: 'Open Farcaster', + color: Color(0xFF855DCD), + borderRadius: (radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular( + BaseButtonSize.big.height / 2)), + onTap: () async { + await ReownCoreUtils.openURL(widget.farcasterUri); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart index 478378a4..c3dc344e 100644 --- a/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart +++ b/packages/reown_appkit/lib/modal/pages/get_wallet_page.dart @@ -3,10 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:reown_appkit/modal/services/uri_service/url_utils_singleton.dart'; import 'package:reown_appkit/reown_appkit.dart'; -import 'package:url_launcher/url_launcher.dart'; - import 'package:reown_appkit/modal/constants/key_constants.dart'; import 'package:reown_appkit/modal/models/grid_item.dart'; import 'package:reown_appkit/modal/constants/style_constants.dart'; @@ -53,30 +50,31 @@ class GetWalletPage extends StatelessWidget { ? data.listing.appStore : data.listing.playStore; if ((url ?? '').isNotEmpty) { - uriService.instance.launchUrl( - Uri.parse(url!), - mode: LaunchMode.externalApplication, - ); + ReownCoreUtils.openURL(url!); } }, bottomItems: [ - AllWalletsItem( - title: 'Explore all', - onTap: () => uriService.instance.launchUrl( - Uri.parse(UrlConstants.exploreWallets), - mode: LaunchMode.externalApplication, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, ), - trailing: Padding( - padding: const EdgeInsets.only(right: 8.0), - child: SvgPicture.asset( - 'lib/modal/assets/icons/arrow_top_right.svg', - package: 'reown_appkit', - colorFilter: ColorFilter.mode( - themeColors.foreground200, - BlendMode.srcIn, + child: AllWalletsItem( + title: 'Explore all', + onTap: () => ReownCoreUtils.openURL( + UrlConstants.exploreWallets, + ), + trailing: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: SvgPicture.asset( + 'lib/modal/assets/icons/arrow_top_right.svg', + package: 'reown_appkit', + colorFilter: ColorFilter.mode( + themeColors.foreground200, + BlendMode.srcIn, + ), + width: 18.0, + height: 18.0, ), - width: 18.0, - height: 18.0, ), ), ), diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart index 30b6b007..e1eed70b 100644 --- a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_main_wallets_page.dart @@ -1,18 +1,18 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/pages/about_wallets.dart'; -import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; import 'package:reown_appkit/modal/pages/connect_wallet_page.dart'; -import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; -import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; import 'package:reown_appkit/modal/constants/style_constants.dart'; -import 'package:reown_appkit/modal/widgets/miscellaneous/input_email.dart'; +import 'package:reown_appkit/modal/widgets/buttons/email_login_input_field.dart'; +import 'package:reown_appkit/modal/widgets/buttons/social_login_buttons_view.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; import 'package:reown_appkit/modal/widgets/modal_provider.dart'; @@ -35,24 +35,30 @@ class ReownAppKitModalMainWalletsPage extends StatefulWidget { class _AppKitModalMainWalletsPageState extends State { + IMagicService get _magicService => GetIt.I(); + @override void initState() { super.initState(); - magicService.instance.isEnabled.addListener(_mailEnabledListener); + _magicService.isEmailEnabled.addListener(_enabledListener); + _magicService.isSocialEnabled.addListener(_enabledListener); } - void _mailEnabledListener() { + void _enabledListener() { setState(() {}); } @override void dispose() { - magicService.instance.isEnabled.removeListener(_mailEnabledListener); + _magicService.isSocialEnabled.removeListener(_enabledListener); + _magicService.isEmailEnabled.removeListener(_enabledListener); super.dispose(); } @override Widget build(BuildContext context) { + final modalInstance = ModalProvider.of(context).instance; + final themeColors = ReownAppKitModalTheme.colorsOf(context); final service = ModalProvider.of(context).instance; final isPortrait = ResponsiveData.isPortrait(context); double maxHeight = isPortrait @@ -82,13 +88,31 @@ class _AppKitModalMainWalletsPageState ), ); } + final emailEnabled = _magicService.isEmailEnabled.value; + final socials = _magicService.socials; + if (!modalInstance.featuresConfig.showMainWallets && + (emailEnabled || socials.isNotEmpty)) { + items.clear(); + } final itemsCount = min(kShortWalletListCount, items.length); if (itemsCount < kShortWalletListCount) { maxHeight = kListItemHeight * (itemsCount + 1.5); } - final emailEnabled = magicService.instance.isEnabled.value; if (emailEnabled) { - maxHeight += (kSearchFieldHeight * 2); + maxHeight += kListItemHeight; + } else { + maxHeight -= 10.0; + } + final socialEnabled = _magicService.isSocialEnabled.value; + if (socialEnabled) { + final length = socials.length; + if (length <= 4) { + maxHeight += (kListItemHeight * 2); + } else { + maxHeight += (kListItemHeight * 3); + } + } else { + maxHeight += 30.0; } final itemsToShow = items.getRange(0, itemsCount); return ConstrainedBox( @@ -98,35 +122,78 @@ class _AppKitModalMainWalletsPageState service.selectWallet(data); widgetStack.instance.push(const ConnectWalletPage()); }, - firstItem: _EmailLoginWidget(), + firstItem: Column( + children: [ + EmailLoginInputField(), + Visibility( + visible: emailEnabled || socialEnabled, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, + ), + child: Column( + children: [ + SocialLoginButtonsView(), + _LoginDivider(), + ], + ), + ), + ), + ], + ), itemList: itemsToShow.toList(), bottomItems: [ - AllWalletsItem( - trailing: (items.length <= kShortWalletListCount) - ? null - : ValueListenableBuilder( - valueListenable: - explorerService.instance.totalListings, - builder: (context, value, _) { - return WalletItemChip(value: value.lazyCount); + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: (!modalInstance.featuresConfig.showMainWallets && + (emailEnabled || socials.isNotEmpty)) + ? AllWalletsItem( + title: 'Connect wallet', + titleAlign: TextAlign.center, + leading: RoundedIcon( + padding: 10.0, + assetPath: + 'lib/modal/assets/icons/regular/wallet.svg', + assetColor: themeColors.foreground100, + circleColor: Colors.transparent, + borderColor: Colors.transparent, + ), + onTap: () { + widgetStack.instance.push( + const ReownAppKitModalAllWalletsPage(), + event: ClickAllWalletsEvent(), + ); + }, + ) + : AllWalletsItem( + trailing: (items.length <= kShortWalletListCount) + ? null + : ValueListenableBuilder( + valueListenable: + explorerService.instance.totalListings, + builder: (context, value, _) { + return WalletItemChip( + value: value.lazyCount, + ); + }, + ), + onTap: () { + if (items.length <= kShortWalletListCount) { + widgetStack.instance.push( + const ReownAppKitModalQRCodePage(), + event: SelectWalletEvent( + name: 'WalletConnect', + platform: AnalyticsPlatform.qrcode, + ), + ); + } else { + widgetStack.instance.push( + const ReownAppKitModalAllWalletsPage(), + event: ClickAllWalletsEvent(), + ); + } }, ), - onTap: () { - if (items.length <= kShortWalletListCount) { - widgetStack.instance.push( - const ReownAppKitModalQRCodePage(), - event: SelectWalletEvent( - name: 'WalletConnect', - platform: AnalyticsPlatform.qrcode, - ), - ); - } else { - widgetStack.instance.push( - const ReownAppKitModalAllWalletsPage(), - event: ClickAllWalletsEvent(), - ); - } - }, ), ], ), @@ -173,75 +240,3 @@ extension on int { return '${toString().substring(0, toString().length - 1)}0+'; } } - -class _EmailLoginWidget extends StatefulWidget { - @override - State<_EmailLoginWidget> createState() => __EmailLoginWidgetState(); -} - -class __EmailLoginWidgetState extends State<_EmailLoginWidget> { - bool _submitted = false; - @override - void initState() { - super.initState(); - magicService.instance.step.addListener(_stepListener); - } - - void _stepListener() { - debugPrint(magicService.instance.step.value.toString()); - if ((magicService.instance.step.value == EmailLoginStep.verifyDevice || - magicService.instance.step.value == EmailLoginStep.verifyOtp || - magicService.instance.step.value == EmailLoginStep.verifyOtp2) && - _submitted) { - widgetStack.instance.push(ConfirmEmailPage()); - _submitted = false; - } - } - - @override - void dispose() { - magicService.instance.step.removeListener(_stepListener); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: magicService.instance.isEnabled, - builder: (context, emailEnabled, _) { - if (!emailEnabled) { - return const SizedBox.shrink(); - } - return Column( - children: [ - InputEmailWidget( - onFocus: (value) { - if (value) { - analyticsService.instance.sendEvent( - EmailLoginSelected(), - ); - } - }, - onValueChange: (value) { - magicService.instance.setEmail(value); - }, - onSubmitted: (value) { - setState(() => _submitted = true); - final service = ModalProvider.of(context).instance; - final chainId = service.selectedChain?.chainId; - analyticsService.instance.sendEvent(EmailSubmitted()); - magicService.instance.connectEmail( - value: value, - chainId: chainId, - ); - }, - ), - const SizedBox.square(dimension: 4.0), - _LoginDivider(), - const SizedBox.square(dimension: kListViewSeparatorHeight), - ], - ); - }, - ); - } -} diff --git a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart index 52a4bb15..e3c0dd81 100644 --- a/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart +++ b/packages/reown_appkit/lib/modal/pages/public/appkit_modal_select_network_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:reown_appkit/modal/constants/key_constants.dart'; import 'package:reown_appkit/modal/constants/string_constants.dart'; @@ -8,7 +10,6 @@ import 'package:reown_appkit/modal/constants/style_constants.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; - import 'package:reown_appkit/modal/widgets/lists/networks_grid.dart'; import 'package:reown_appkit/modal/widgets/value_listenable_builders/network_service_items_listener.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; @@ -24,17 +25,27 @@ class ReownAppKitModalSelectNetworkPage extends StatelessWidget { final Function(ReownAppKitModalNetworkInfo)? onTapNetwork; void _onSelectNetwork( - BuildContext context, ReownAppKitModalNetworkInfo chainInfo) async { + BuildContext context, + ReownAppKitModalNetworkInfo chainInfo, + ) async { final service = ModalProvider.of(context).instance; if (service.isConnected) { final approvedChains = service.session!.getApprovedChains() ?? []; final caip2Chain = '${CoreConstants.namespace}:${chainInfo.chainId}'; final isChainApproved = approvedChains.contains(caip2Chain); if (chainInfo.chainId == service.selectedChain?.chainId) { - widgetStack.instance.pop(); + if (widgetStack.instance.canPop()) { + widgetStack.instance.pop(); + } else { + service.closeModal(); + } } else if (isChainApproved || service.session!.sessionService.isMagic) { await service.selectChain(chainInfo, switchChain: true); - widgetStack.instance.pop(); + if (widgetStack.instance.canPop()) { + widgetStack.instance.pop(); + } else { + service.closeModal(); + } } else { widgetStack.instance.push(ConnectNetworkPage(chainInfo: chainInfo)); } @@ -50,8 +61,6 @@ class ReownAppKitModalSelectNetworkPage extends StatelessWidget { final service = ModalProvider.of(context).instance; final isSwitch = service.selectedChain != null; final isPortrait = ResponsiveData.isPortrait(context); - final maxHeight = - (ResponsiveData.gridItemSzieOf(context).height * 3) + (kPadding12 * 4); return ModalNavbar( title: isSwitch ? 'Change network' : 'Select network', @@ -62,22 +71,25 @@ class ReownAppKitModalSelectNetworkPage extends StatelessWidget { children: [ Flexible( fit: isPortrait ? FlexFit.loose : FlexFit.tight, - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: maxHeight), - child: NetworkServiceItemsListener( - builder: (context, initialised, items) { - if (!initialised) { - return const ContentLoading(); - } - return NetworksGrid( + child: NetworkServiceItemsListener( + builder: (context, initialised, items) { + if (!initialised) return const ContentLoading(); + // + final rows = min((items.length ~/ 4) + 1, 3); + final height = + (ResponsiveData.gridItemSzieOf(context).height * rows); + final maxHeight = height + (kPadding12 * (rows + 1)); + return ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: NetworksGrid( onTapNetwork: (chainInfo) => _onSelectNetwork( context, chainInfo, ), itemList: items, - ); - }, - ), + ), + ); + }, ), ), Divider(color: themeColors.grayGlass005, height: 0.0), diff --git a/packages/reown_appkit/lib/modal/pages/social_login_page.dart b/packages/reown_appkit/lib/modal/pages/social_login_page.dart new file mode 100644 index 00000000..c89cdf19 --- /dev/null +++ b/packages/reown_appkit/lib/modal/pages/social_login_page.dart @@ -0,0 +1,507 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/constants/key_constants.dart'; +import 'package:reown_appkit/modal/constants/string_constants.dart'; + +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/pages/farcaster_qrcode_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/utils/platform_utils.dart'; +import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; +import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/content_loading.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/avatars/loading_border.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar.dart'; +import 'package:reown_appkit/modal/widgets/navigation/navbar_action_button.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; + +class SocialLoginPage extends StatefulWidget { + const SocialLoginPage({ + required this.socialOption, + this.farcasterCompleter, + }) : super(key: KeyConstants.socialLoginPage); + final AppKitSocialOption socialOption; + final Future? farcasterCompleter; + + @override + State createState() => _SocialLoginPageState(); +} + +class _SocialLoginPageState extends State { + IMagicService get _magicService => GetIt.I(); + + IReownAppKitModal? _service; + ModalError? errorEvent; + bool _retrievingData = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + _service = ModalProvider.of(context).instance; + _service?.onModalError.subscribe(_errorListener); + if (widget.farcasterCompleter == null) { + _initSocialLogin(widget.socialOption); + setState(() {}); + } else { + widget.farcasterCompleter!.then((success) async { + await _completeFarcasterLogin(success); + }); + } + }); + } + + @override + void dispose() { + _service?.onModalError.unsubscribe(_errorListener); + super.dispose(); + } + + void _errorListener(ModalError? event) { + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: event?.message ?? 'Something went wrong.', + )); + setState(() => errorEvent = event); + } + + Future _initSocialLogin(AppKitSocialOption option) async { + try { + setState(() => errorEvent = null); + analyticsService.instance.sendEvent(SocialLoginStarted( + provider: widget.socialOption.name.toLowerCase(), + )); + if (option == AppKitSocialOption.Farcaster) { + final farcasterUri = await _magicService.getFarcasterUri( + chainId: _service?.selectedChain?.chainId, + ); + + if (farcasterUri != null) { + await _continueInFarcaster(farcasterUri); + } + } else { + final schema = null; // _service?.appKit?.metadata.redirect?.universal; + final redirectUri = await _magicService.getSocialRedirectUri( + provider: option, + schema: schema, + chainId: _service?.selectedChain?.chainId, + ); + + if (redirectUri != null) { + if (schema == null && option.supportsWebView) { + await _continueInWebview(redirectUri); + // final result = await ReownSocialLogin.login(initialUrl: redirectUri); + // debugPrint('ReownSocialLogin $result'); + // if (result == null) { + // _cancelSocialLogin(); + // } else { + // await _completeSocialLogin(result); + // } + } else { + _magicService.onCompleteSocialLogin.subscribe( + _onCompleteSocialLogin, + ); + await ReownCoreUtils.openURL(redirectUri); + } + } + } + } catch (e) { + debugPrint('[$runtimeType] _initSocialLogin error $e'); + } + } + + Future _continueInWebview(String redirectUri) async { + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final bottomSheet = PlatformUtils.isBottomSheet(); + final isTabletSize = PlatformUtils.isTablet(context); + final maxRadius = min(radiuses.radiusM, 36.0); + final innerContainerBorderRadius = bottomSheet && !isTabletSize + ? BorderRadius.only( + topLeft: Radius.circular(maxRadius), + topRight: Radius.circular(maxRadius), + ) + : BorderRadius.all(Radius.circular(maxRadius)); + final result = await showModalBottomSheet( + backgroundColor: Colors.black.withOpacity(0.5), + isDismissible: false, + isScrollControlled: true, + enableDrag: false, + useRootNavigator: false, + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.9, + ), + context: context, + builder: (context) => Container( + decoration: BoxDecoration( + color: themeColors.background125, + borderRadius: innerContainerBorderRadius, + ), + child: _WebViewLoginWidget( + url: redirectUri, + onCancel: () { + Navigator.of(context).pop(false); + }, + ), + ), + ); + + if (result == false) { + _cancelSocialLogin(); + } else { + await _completeSocialLogin(result); + } + } + + void _onCompleteSocialLogin(CompleteSocialLoginEvent? event) async { + if (event != null) { + await _completeSocialLogin(event.url); + } else { + _cancelSocialLogin(); + } + } + + Future _completeSocialLogin(String url) async { + try { + setState(() => _retrievingData = true); + final success = await _magicService.connectSocial( + uri: '?${Uri.parse(url).query}', + ); + if (success == true) { + await _magicService.getUser(); + analyticsService.instance.sendEvent(SocialLoginSuccess( + provider: widget.socialOption.name.toLowerCase(), + )); + _magicService.onCompleteSocialLogin.unsubscribe( + _onCompleteSocialLogin, + ); + } else { + _cancelSocialLogin(); + } + } catch (e) { + debugPrint('[$runtimeType] _completeSocialLogin error $e'); + _cancelSocialLogin(); + setState(() => _retrievingData = false); + } + } + + Future _continueInFarcaster(String farcasterUri) async { + widgetStack.instance.push( + replace: true, + FarcasterQRCodePage( + farcasterUri: farcasterUri, + farcasterCompleter: _magicService.awaitFarcasterResponse(), + ), + ); + } + + Future _completeFarcasterLogin(bool success) async { + if (success == false) { + _cancelSocialLogin(); + } else { + setState(() => _retrievingData = true); + await _magicService.getUser(); + } + } + + void _cancelSocialLogin() { + debugPrint('[$runtimeType] _cancelSocialLogin'); + errorEvent = ModalError('User canceled'); + setState(() => _retrievingData = false); + analyticsService.instance.sendEvent(SocialLoginError( + provider: widget.socialOption.name.toLowerCase(), + )); + } + + @override + Widget build(BuildContext context) { + if (_service == null) { + return ContentLoading(); + } + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final isPortrait = ResponsiveData.isPortrait(context); + final maxWidth = isPortrait + ? ResponsiveData.maxWidthOf(context) + : ResponsiveData.maxHeightOf(context) - + kNavbarHeight - + (kPadding16 * 2); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + return ModalNavbar( + title: widget.socialOption.name, + noBack: true, + body: SingleChildScrollView( + scrollDirection: isPortrait ? Axis.vertical : Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: kPadding16), + child: Flex( + direction: isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + Container( + constraints: BoxConstraints(maxWidth: maxWidth), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox.square(dimension: 20.0), + errorEvent == null + ? LoadingBorder( + animate: errorEvent == null, + borderRadius: kSelectedWalletIconHeight, + child: ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(maxWidth), + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + '${widget.socialOption.name.toLowerCase()}_logo.svg', + ), + package: 'reown_appkit', + ), + ), + ) + : Stack( + children: [ + SizedBox.square( + dimension: kSelectedWalletIconHeight, + child: ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(maxWidth), + child: SvgPicture.asset( + AssetUtils.getThemedAsset( + context, + '${widget.socialOption.name.toLowerCase()}_logo.svg', + ), + package: 'reown_appkit', + ), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: themeColors.background125, + borderRadius: + BorderRadius.all(Radius.circular(30.0)), + ), + padding: const EdgeInsets.all(1.0), + clipBehavior: Clip.antiAlias, + child: RoundedIcon( + assetPath: 'lib/modal/assets/icons/close.svg', + assetColor: themeColors.error100, + circleColor: + themeColors.error100.withOpacity(0.2), + borderColor: themeColors.background125, + padding: 4.0, + size: 24.0, + ), + ), + ), + ], + ), + const SizedBox.square(dimension: 20.0), + Text( + 'Log in with ${widget.socialOption.name}', + textAlign: TextAlign.center, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + const SizedBox.square(dimension: 8.0), + errorEvent == null + ? Text( + _retrievingData + ? 'Retrieving user data' + : 'Connect in the provider window', + textAlign: TextAlign.center, + style: themeData.textStyles.small500.copyWith( + color: themeColors.foreground200, + ), + ) + : SimpleIconButton( + onTap: () { + _initSocialLogin(widget.socialOption); + }, + leftIcon: 'lib/modal/assets/icons/refresh_back.svg', + title: 'Try again', + backgroundColor: Colors.transparent, + foregroundColor: themeColors.accent100, + ), + const SizedBox.square(dimension: kPadding16), + ], + ), + ), + if (!isPortrait) const SizedBox.square(dimension: kPadding16), + ], + ), + ), + ); + } +} + +class _WebViewLoginWidget extends StatefulWidget { + const _WebViewLoginWidget({ + required this.url, + required this.onCancel, + }); + final String url; + final VoidCallback onCancel; + + @override + State<_WebViewLoginWidget> createState() => __WebViewLoginWidgetState(); +} + +class __WebViewLoginWidgetState extends State<_WebViewLoginWidget> { + final _webViewController = WebViewController(); + final _cookieManager = WebViewCookieManager(); + + @override + void initState() { + super.initState(); + _init(); + } + + // ignore: unused_element + Future _clearCookies() async { + if (!kDebugMode) return; + try { + if (WebViewPlatform.instance is WebKitWebViewPlatform) { + final webKitManager = + _cookieManager.platform as WebKitWebViewCookieManager; + webKitManager.clearCookies(); + } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + final androidManager = + _cookieManager.platform as AndroidWebViewCookieManager; + androidManager.clearCookies(); + androidManager.setAcceptThirdPartyCookies( + _webViewController.platform as AndroidWebViewController, + kDebugMode, + ); + } + } catch (e) { + debugPrint('[$runtimeType] _clearCookies error $e'); + } + } + + void _setDebugMode() { + if (!kDebugMode) return; + if (Platform.isIOS) { + final wkCtrl = _webViewController.platform as WebKitWebViewController; + wkCtrl.setInspectable(true); + } + if (Platform.isAndroid) { + if (_webViewController.platform is AndroidWebViewController) { + final aCtrl = _webViewController.platform as AndroidWebViewController; + aCtrl.setMediaPlaybackRequiresUserGesture(false); + AndroidWebViewController.enableDebugging(true); + + final cookieManager = + _cookieManager.platform as AndroidWebViewCookieManager; + cookieManager.setAcceptThirdPartyCookies( + _webViewController.platform as AndroidWebViewController, + true, + ); + } + } + } + + Future _fitToScreen() async { + return await _webViewController.runJavaScript(''' + if (document.querySelector('meta[name="viewport"]') === null) { + var meta = document.createElement('meta'); + meta.name = 'viewport'; + meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; + document.head.appendChild(meta); + } else { + document.querySelector('meta[name="viewport"]').setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + } + '''); + } + + Future _init() async { + _setDebugMode(); + // await _clearCookies(); + // await _webViewController.clearCache(); + // await _webViewController.clearLocalStorage(); + await _webViewController.enableZoom(false); + await _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + await _fitToScreen(); + await _webViewController.setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (NavigationRequest request) { + final uri = Uri.parse(request.url); + final params = uri.queryParameters; + final secureOrigin1 = UrlConstants.secureOrigin1; + final secureOrigin2 = UrlConstants.secureOrigin2; + if ((uri.authority == secureOrigin1 || + uri.authority == secureOrigin2) && + params.containsKey('state')) { + Future.delayed(Duration(milliseconds: 500), () { + Navigator.of(context).pop(request.url); + }); + } + return NavigationDecision.navigate; + }, + ), + ); + await _webViewController.loadRequest(Uri.parse(widget.url)); + } + + @override + Widget build(BuildContext context) { + return ModalNavbar( + title: '', + noBack: true, + noClose: true, + rightAction: NavbarActionButton( + asset: 'lib/modal/assets/icons/close.svg', + action: widget.onCancel, + ), + body: WebViewWidget(controller: _webViewController), + ); + } +} + +extension _AppKitSocialOptionExtension on AppKitSocialOption { + bool get supportsWebView { + switch (this) { + case AppKitSocialOption.X: + return true; + case AppKitSocialOption.Apple: + return true; + case AppKitSocialOption.Discord: + return true; + case AppKitSocialOption.Farcaster: + return true; + // case AppKitSocialOption.GitHub: + // return true; + // case AppKitSocialOption.Facebook: + // return true; + // case AppKitSocialOption.Twitch: + // return true; + // case AppKitSocialOption.Telegram: + // return true; + // case AppKitSocialOption.Google: + // return false; + } + } +} diff --git a/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart b/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart index acfa922b..754c256f 100644 --- a/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart +++ b/packages/reown_appkit/lib/modal/pages/upgrade_wallet_page.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:reown_appkit/reown_appkit.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - import 'package:reown_appkit/modal/constants/key_constants.dart'; import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; @@ -39,10 +37,7 @@ class UpgradeWalletPage extends StatelessWidget { leftIcon: 'lib/modal/assets/icons/wc.svg', onTap: () { analyticsService.instance.sendEvent(EmailUpgradeFromModal()); - launchUrlString( - UrlConstants.secureDashboard, - mode: LaunchMode.externalApplication, - ); + ReownCoreUtils.openURL(UrlConstants.secureDashboard); }, rightIcon: 'lib/modal/assets/icons/arrow_top_right.svg', title: Uri.parse(UrlConstants.secureDashboard).authority, diff --git a/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart index 49ca02d6..e10afa94 100644 --- a/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart +++ b/packages/reown_appkit/lib/modal/services/analytics_service/analytics_service.dart @@ -48,9 +48,9 @@ class AnalyticsService implements IAnalyticsService { _isEnabled = enableAnalytics!; } _bundleId = await ReownCoreUtils.getPackageName(); - _core.logger.d('[$runtimeType] enabled: $_isEnabled'); + _core.logger.t('[$runtimeType] enabled: $_isEnabled'); } catch (e, _) { - _core.logger.d('[$runtimeType] init error', error: e); + _core.logger.e('[$runtimeType] init error', error: e); } } @@ -65,7 +65,7 @@ class AnalyticsService implements IAnalyticsService { final enabled = json['isAnalyticsEnabled'] as bool?; return enabled ?? false; } catch (e, _) { - _core.logger.d('[$runtimeType] fetch error', error: e); + _core.logger.e('[$runtimeType] fetch error', error: e); return false; } } @@ -90,9 +90,9 @@ class AnalyticsService implements IAnalyticsService { if (code == 200 || code == 202) { _eventsController.sink.add(analyticsEvent.toMap()); } - _core.logger.d('[$runtimeType] send event $code: $body'); + _core.logger.t('[$runtimeType] send event $code: $body'); } catch (e, _) { - _core.logger.d('[$runtimeType] send event error', error: e); + _core.logger.e('[$runtimeType] send event error', error: e); } } } diff --git a/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart b/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart index 2af5a772..1d60ef4b 100644 --- a/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart +++ b/packages/reown_appkit/lib/modal/services/analytics_service/models/analytics_event.dart @@ -587,3 +587,72 @@ class SiweAuthError implements AnalyticsEvent { if (properties != null) 'properties': properties, }; } + +class SocialLoginStarted implements AnalyticsEvent { + final String _provider; + SocialLoginStarted({required String provider}) : _provider = provider; + + @override + String get type => 'track'; + + @override + String get event => 'LOGIN_STARTED'; + + @override + Map? get properties => { + 'provider': _provider, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SocialLoginSuccess implements AnalyticsEvent { + final String _provider; + SocialLoginSuccess({required String provider}) : _provider = provider; + + @override + String get type => 'track'; + + @override + String get event => 'LOGIN_SUCCESS'; + + @override + Map? get properties => { + 'provider': _provider, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} + +class SocialLoginError implements AnalyticsEvent { + final String _provider; + SocialLoginError({required String provider}) : _provider = provider; + + @override + String get type => 'track'; + + @override + String get event => 'LOGIN_ERROR'; + + @override + Map? get properties => { + 'provider': _provider, + }; + + @override + Map toMap() => { + 'type': type, + 'event': event, + if (properties != null) 'properties': properties, + }; +} diff --git a/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart index 6923eed6..f1193c0f 100644 --- a/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart +++ b/packages/reown_appkit/lib/modal/services/blockchain_service/blockchain_service.dart @@ -89,6 +89,10 @@ class BlockChainService implements IBlockChainService { final amount = EtherAmount.fromBigInt(EtherUnit.wei, hexToInt(result)); return amount.getValueInUnit(EtherUnit.ether); } catch (e) { + _core.logger.e( + '[$runtimeType] Failed to get parse ${request.toJson()}. ' + 'Response: ${response.body}, Status code: ${response.statusCode}', + ); rethrow; } } else { @@ -97,9 +101,8 @@ class BlockChainService implements IBlockChainService { _retries -= 1; await rpcRequest(chainId: chainId, request: request); } else { - _core.logger.i( - '[$runtimeType] Failed to get request ${request.toJson()}. ' - 'Response: ${response.body}, Status code: ${response.statusCode}', + _core.logger.e( + '[$runtimeType] Failed to get request ${request.toJson()}. Response: ${response.body}, Status code: ${response.statusCode}', ); } } diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart index 652def34..79a13f50 100644 --- a/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart +++ b/packages/reown_appkit/lib/modal/services/explorer_service/explorer_service.dart @@ -155,7 +155,6 @@ class ExplorerService implements IExplorerService { sampleWallets.add(sampleWallet); } } - _core.logger.d('[$runtimeType] sample wallets: ${sampleWallets.length}'); return sampleWallets; } @@ -215,10 +214,6 @@ class ExplorerService implements IExplorerService { ); return apiResponse.data.toList(); } else { - _core.logger.d( - 'â›” [$runtimeType] error fetching native data ${response.request?.url}', - error: response.statusCode, - ); return []; } } catch (e) { @@ -248,8 +243,8 @@ class ExplorerService implements IExplorerService { ); // this query gives me a count of installedWalletsParam.length final installedWallets = await _fetchListings(params: params); - _core.logger.d( - '[$runtimeType] installed wallets: ${installedWallets.length}', + _core.logger.t( + '[$runtimeType] ${installedWallets.length} installed wallets', ); return installedWallets.setInstalledFlag(); } @@ -291,7 +286,6 @@ class ExplorerService implements IExplorerService { final uri = Uri.parse('${UrlConstants.apiService}/getWallets').replace( queryParameters: queryParams, ); - _core.logger.d('[$runtimeType] fetching $uri'); try { final response = await _client.get(uri, headers: headers); if (response.statusCode == 200 || response.statusCode == 202) { @@ -304,15 +298,10 @@ class ExplorerService implements IExplorerService { } return apiResponse.data.toList().toAppKitWalletInfo(); } else { - _core.logger.d( - 'â›” [$runtimeType] error fetching listings (${response.statusCode}) ${response.request?.url}\n' - 'headers: $headers\n' - 'queryParams $queryParams', - ); return []; } } catch (e) { - _core.logger.d( + _core.logger.e( '[$runtimeType] error fetching listings: $uri', error: e, ); @@ -353,7 +342,7 @@ class ExplorerService implements IExplorerService { } } catch (e, s) { _core.logger.e( - '[$runtimeType] error getConnectedWallet:', + '[$runtimeType] error get connected wallet:', error: e, stackTrace: s, ); @@ -383,7 +372,7 @@ class ExplorerService implements IExplorerService { _listings = currentListings; listings.value = _listings; } catch (e) { - _core.logger.e('[$runtimeType] updating recent wallet: $e'); + _core.logger.e('[$runtimeType] error updating recent wallet: $e'); } } @@ -407,7 +396,6 @@ class ExplorerService implements IExplorerService { final excludedIds = (excludedWalletIds ?? {}); final exclude = excludedIds.isNotEmpty ? excludedIds.join(',') : null; - _core.logger.d('[$runtimeType] search $query'); _currentSearchValue = query; final newListins = await _fetchListings( params: RequestParams( @@ -423,7 +411,6 @@ class ExplorerService implements IExplorerService { listings.value = newListins; _debouncer.run(() => isSearching.value = false); - _core.logger.d('[$runtimeType] _searchListings $query'); } @override diff --git a/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart b/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart index d8a8f7ab..1d407594 100644 --- a/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart +++ b/packages/reown_appkit/lib/modal/services/explorer_service/models/wc_sample_wallets.dart @@ -34,9 +34,8 @@ class WCSampleWallets { 'platform': ['android'], 'id': '123456789012345678901234567894', 'schema': 'kotlin-web3wallet://wc', - 'bundleId': 'com.walletconnect.sample.wallet.internal', - 'universal': - 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_internal', + 'bundleId': 'com.reown.sample.wallet.internal', + 'universal': 'https://appkit-lab.reown.com/wallet_internal', }, ]; @@ -70,9 +69,8 @@ class WCSampleWallets { 'platform': ['android'], 'id': '123456789012345678901234567893', 'schema': 'kotlin-web3wallet://wc', - 'bundleId': 'com.walletconnect.sample.wallet', - 'universal': - 'https://web3modal-laboratory-git-chore-kotlin-assetlinks-walletconnect1.vercel.app/wallet_release', + 'bundleId': 'com.reown.sample.wallet', + 'universal': 'https://appkit-lab.reown.com/wallet_release', }, ]; diff --git a/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart b/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart index 9fc53549..51656f10 100644 --- a/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart +++ b/packages/reown_appkit/lib/modal/services/magic_service/i_magic_service.dart @@ -1,16 +1,41 @@ +import 'package:flutter/foundation.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/reown_appkit.dart'; +import 'package:webview_flutter/webview_flutter.dart'; abstract class IMagicService { - ConnectionMetadata get metadata; + List get supportedMethods; + List get socials; + + ValueNotifier get isReady; + ValueNotifier get isConnected; + ValueNotifier get isTimeout; + ValueNotifier get isEmailEnabled; + ValueNotifier get isSocialEnabled; + ValueNotifier get email; + ValueNotifier get newEmail; + ValueNotifier get step; + + WebViewWidget get webview; Future init(); void setEmail(String value); void setNewEmail(String value); + void setProvider(AppKitSocialOption? provider); + + Future getSocialRedirectUri({ + required AppKitSocialOption provider, + String? schema, + String? chainId, + }); + Future connectSocial({required String uri}); + void completeSocialLogin({required String url}); + Future getFarcasterUri({String? chainId}); + Future awaitFarcasterResponse(); - // ****** W3mFrameProvider public methods ******* // - Future connectEmail({required String value}); + Future connectEmail({required String value, String? chainId}); Future updateEmail({required String value}); Future updateEmailPrimaryOtp({required String otp}); Future updateEmailSecondaryOtp({required String otp}); @@ -31,4 +56,5 @@ abstract class IMagicService { abstract final Event onMagicUpdate; abstract final Event onMagicError; abstract final Event onMagicRpcRequest; + abstract final Event onCompleteSocialLogin; } diff --git a/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart b/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart index 800f3974..af8ecdf4 100644 --- a/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart +++ b/packages/reown_appkit/lib/modal/services/magic_service/magic_service.dart @@ -4,8 +4,8 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; + import 'package:reown_appkit/reown_appkit.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:reown_appkit/modal/constants/string_constants.dart'; import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; @@ -14,64 +14,81 @@ import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_data.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/modal/services/magic_service/models/frame_message.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; class MagicService implements IMagicService { - static const _safeDomains = [ + static const _thirdSafeDomains = [ 'auth.magic.link', 'launchdarkly.com', ]; - static const supportedMethods = [ - 'personal_sign', - 'eth_sign', - 'eth_sendTransaction', - 'eth_signTypedData_v4', - 'wallet_switchEthereumChain', - 'wallet_addEthereumChain', - ]; - static const defaultWalletData = ReownAppKitModalWalletInfo( - listing: Listing( - id: '', - name: 'Email Wallet', - homepage: '', - imageId: '', - order: 10000, - ), - installed: false, - recent: false, - ); - - @override - ConnectionMetadata get metadata => ConnectionMetadata( + + ConnectionMetadata get _selfMetadata => ConnectionMetadata( + metadata: _metadata, + publicKey: '', + ); + + // TODO export this + ConnectionMetadata get _peerMetadata => ConnectionMetadata( metadata: PairingMetadata( - name: defaultWalletData.listing.name, + name: 'Email Wallet', description: '', - url: defaultWalletData.listing.homepage, - icons: [''], + url: '', + icons: [''], // TODO set the icon here depending on the login type ), publicKey: '', ); // - // final IReownAppKit _appKit; Timer? _timeOutTimer; String? _connectionChainId; int _onLoadCount = 0; String _packageName = ''; - - late final WebViewController _webViewController; - WebViewController get controller => _webViewController; - - late final WebViewWidget _webview; - WebViewWidget get webview => _webview; + AppKitSocialOption? _socialProvider; late Completer _initialized; late Completer _connected; late Completer _response; late Completer _disconnect; + late final IReownCore _core; + late final PairingMetadata _metadata; + late final FeaturesConfig _features; + late final WebViewController _webViewController; + late final WebViewWidget _webview; + // Logger get _logger => _core.logger; + + @override + final List supportedMethods = [ + 'personal_sign', + 'eth_sendTransaction', + 'eth_accounts', + 'eth_sendRawTransaction', + 'eth_signTypedData_v4', + ]; + + @override + WebViewWidget get webview => _webview; + @override + final isReady = ValueNotifier(false); + @override + final isConnected = ValueNotifier(false); + @override + final isTimeout = ValueNotifier(false); + @override + final isEmailEnabled = ValueNotifier(false); + @override + final isSocialEnabled = ValueNotifier(false); + @override + final email = ValueNotifier(''); + @override + final newEmail = ValueNotifier(''); + @override + final step = ValueNotifier(EmailLoginStep.idle); + + @override + List get socials => _features.socials; @override Event onMagicLoginRequest = Event(); @@ -91,27 +108,21 @@ class MagicService implements IMagicService { @override Event onMagicRpcRequest = Event(); - final isEnabled = ValueNotifier(false); - final isReady = ValueNotifier(false); - final isConnected = ValueNotifier(false); - final isTimeout = ValueNotifier(false); - - final email = ValueNotifier(''); - final newEmail = ValueNotifier(''); - final step = ValueNotifier(EmailLoginStep.idle); - - late final IReownCore _core; - late final PairingMetadata _metadata; + @override + Event onCompleteSocialLogin = + Event(); MagicService({ - // required IReownAppKit appKit, required IReownCore core, required PairingMetadata metadata, - bool enabled = false, + required FeaturesConfig featuresConfig, }) : _core = core, - _metadata = metadata { - isEnabled.value = enabled; - if (isEnabled.value) { + _metadata = metadata, + _features = featuresConfig { + isEmailEnabled.value = _features.email; + isSocialEnabled.value = _features.socials.isNotEmpty; + // + if (isEmailEnabled.value || isSocialEnabled.value) { _webViewController = WebViewController(); _webview = WebViewWidget(controller: _webViewController); isReady.addListener(_readyListener); @@ -127,7 +138,7 @@ class MagicService implements IMagicService { @override Future init() async { - if (!isEnabled.value) { + if (!isEmailEnabled.value && !isSocialEnabled.value) { _initialized = Completer(); _initialized.complete(false); _connected = Completer(); @@ -149,21 +160,20 @@ class MagicService implements IMagicService { await _webViewController.setBackgroundColor(Colors.transparent); await _webViewController.setJavaScriptMode(JavaScriptMode.unrestricted); + await _webViewController.enableZoom(false); await _webViewController.addJavaScriptChannel( 'w3mWebview', onMessageReceived: _onFrameMessage, ); await _webViewController.setNavigationDelegate( NavigationDelegate( - onNavigationRequest: (NavigationRequest request) { + onNavigationRequest: (NavigationRequest request) async { if (_isAllowedDomain(request.url)) { + await _fitToScreen(); return NavigationDecision.navigate; } if (isReady.value) { - launchUrlString( - request.url, - mode: LaunchMode.externalApplication, - ); + ReownCoreUtils.openURL(request.url); } return NavigationDecision.prevent; }, @@ -176,6 +186,7 @@ class MagicService implements IMagicService { // This is happening only on Android devices, on iOS only once execution is done no matter what. if (_onLoadCount < 2 && Platform.isAndroid) return; await _runJavascript(); + await _fitToScreen(); Future.delayed(Duration(milliseconds: 600)).then((value) { if (_initialized.isCompleted) return; _initialized.complete(true); @@ -197,11 +208,90 @@ class MagicService implements IMagicService { newEmail.value = value; } + @override + void setProvider(AppKitSocialOption? provider) { + _socialProvider = provider; + } + + bool get _socialsNotReady => (!isSocialEnabled.value || !isReady.value); + bool get _emailNotReady => (!isEmailEnabled.value || !isReady.value); + bool get _serviceNotReady => + (!isEmailEnabled.value && !isSocialEnabled.value) || !isReady.value; + // ****** W3mFrameProvider public methods ******* // + // SOCIAL LOGIN RELATED METHODS + + Completer _getSocialRedirectUri = Completer(); + @override + Future getSocialRedirectUri({ + required AppKitSocialOption provider, + String? schema, + String? chainId, + }) async { + if (_socialsNotReady) return null; + // + _getSocialRedirectUri = Completer(); + _connectionChainId = chainId ?? _connectionChainId; + _socialProvider = provider; + final message = GetSocialRedirectUri( + provider: _socialProvider!.name.toLowerCase(), + schema: schema, + ).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _getSocialRedirectUri.future; + } + + Completer _connectSocial = Completer(); + @override + Future connectSocial({required String uri}) async { + if (_socialsNotReady) return null; + // + _connectSocial = Completer(); + final message = ConnectSocial(uri: uri).toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _connectSocial.future; + } + + @override + void completeSocialLogin({required String url}) { + onCompleteSocialLogin.broadcast(CompleteSocialLoginEvent(url)); + } + + Completer _getFarcasterUri = Completer(); + @override + Future getFarcasterUri({String? chainId}) async { + if (_socialsNotReady) return null; + if (_getFarcasterUri.isCompleted) { + return await _getFarcasterUri.future; + } + // + _getFarcasterUri = Completer(); + _connectionChainId = chainId ?? _connectionChainId; + _socialProvider = AppKitSocialOption.Farcaster; + final message = GetFarcasterUri().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + return await _getFarcasterUri.future; + } + + Completer _connectFarcaster = Completer(); + @override + Future awaitFarcasterResponse() async { + if (_socialsNotReady) return false; + // + _connectFarcaster = Completer(); + // final message = ConnectFarcaster().toString(); + // await _webViewController.runJavaScript('sendMessage($message)'); + return await _connectFarcaster.future; + } + + // EMAIL RELATED METHODS + @override Future connectEmail({required String value, String? chainId}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) return; + // + _socialProvider = null; _connectionChainId = chainId ?? _connectionChainId; final message = ConnectEmail(email: value).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -209,7 +299,8 @@ class MagicService implements IMagicService { @override Future updateEmail({required String value}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) return; + // step.value = EmailLoginStep.loading; final message = UpdateEmail(email: value).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -217,7 +308,8 @@ class MagicService implements IMagicService { @override Future updateEmailPrimaryOtp({required String otp}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) return; + // step.value = EmailLoginStep.loading; final message = UpdateEmailPrimaryOtp(otp: otp).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -225,7 +317,8 @@ class MagicService implements IMagicService { @override Future updateEmailSecondaryOtp({required String otp}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) return; + // step.value = EmailLoginStep.loading; final message = UpdateEmailSecondaryOtp(otp: otp).toString(); await _webViewController.runJavaScript('sendMessage($message)'); @@ -233,28 +326,26 @@ class MagicService implements IMagicService { @override Future connectOtp({required String otp}) async { - if (!isEnabled.value || !isReady.value) return; + if (_emailNotReady) return; + // step.value = EmailLoginStep.loading; final message = ConnectOtp(otp: otp).toString(); await _webViewController.runJavaScript('sendMessage($message)'); } - @override - Future getChainId() async { - if (!isEnabled.value || !isReady.value) return; - final message = GetChainId().toString(); - await _webViewController.runJavaScript('sendMessage($message)'); - } + // SHARED METHODS @override Future syncTheme(ReownAppKitModalTheme? theme) async { - if (!isEnabled.value || !isReady.value) return; + if (_serviceNotReady) return; + // final message = SyncTheme(theme: theme).toString(); await _webViewController.runJavaScript('sendMessage($message)'); } void _syncDappData() async { - if (!isEnabled.value || !isReady.value) return; + if (_serviceNotReady) return; + // final message = SyncAppData( metadata: _metadata, projectId: _core.projectId, @@ -263,20 +354,30 @@ class MagicService implements IMagicService { await _webViewController.runJavaScript('sendMessage($message)'); } + @override + Future getChainId() async { + if (_serviceNotReady) return; + // + final message = GetChainId().toString(); + await _webViewController.runJavaScript('sendMessage($message)'); + } + @override Future getUser({String? chainId}) async { - if (!isEnabled.value || !isReady.value) return; + if (_serviceNotReady) return; + // return await _getUser(chainId); } Future _getUser(String? chainId) async { - final message = GetUser(chainId: chainId).toString(); + final message = GetUser(chainId: chainId ?? _connectionChainId).toString(); return await _webViewController.runJavaScript('sendMessage($message)'); } @override Future switchNetwork({required String chainId}) async { - if (!isEnabled.value || !isReady.value) return; + if (_serviceNotReady) return; + // final message = SwitchNetwork(chainId: chainId).toString(); await _webViewController.runJavaScript('sendMessage($message)'); } @@ -286,7 +387,8 @@ class MagicService implements IMagicService { String? chainId, required SessionRequestParams request, }) async { - if (!isEnabled.value) return; + if (_serviceNotReady) return; + // await _awaitReadyness.future; await _rpcRequest(request.toJson()); return await _response.future; @@ -295,9 +397,17 @@ class MagicService implements IMagicService { Future _rpcRequest(Map parameters) async { _response = Completer(); if (!isConnected.value) { - onMagicLoginRequest.broadcast(MagicSessionEvent(email: email.value)); _connected = Completer(); - await connectEmail(value: email.value); + if (_socialProvider != null) { + onMagicLoginRequest.broadcast(MagicSessionEvent( + provider: _socialProvider, + )); + } else { + onMagicLoginRequest.broadcast(MagicSessionEvent( + email: email.value, + )); + await connectEmail(value: email.value); + } final success = await _connected.future; if (!success) return; } @@ -310,7 +420,8 @@ class MagicService implements IMagicService { @override Future disconnect() async { - if (!isEnabled.value || !isReady.value) return false; + if (_serviceNotReady) return false; + // _disconnect = Completer(); if (!isConnected.value) { _resetTimeOut(); @@ -353,9 +464,11 @@ class MagicService implements IMagicService { await _webViewController.runJavaScript('sendMessage($message)'); } + String? _socialUsername; + void _onFrameMessage(JavaScriptMessage jsMessage) async { if (Platform.isAndroid) { - _core.logger.d('[$runtimeType] jsMessage ${jsMessage.message}'); + _core.logger.d('[$runtimeType] JS Console: $jsMessage'); } try { final frameMessage = jsMessage.toFrameMessage(); @@ -378,6 +491,27 @@ class MagicService implements IMagicService { await _getUser(_connectionChainId); } } + if (messageData.getSocialRedirectUriSuccess) { + final uri = messageData.getPayloadMapKey('uri'); + _getSocialRedirectUri.complete(uri); + } + // ****** CONNECT_SOCIAL_SUCCESS + if (messageData.connectSocialSuccess) { + _socialUsername = messageData.getPayloadMapKey('userName'); + debugPrint('[$runtimeType] connectSocialSuccess $_socialUsername'); + _connectSocial.complete(true); + } + // ****** GET_FARCASTER_URI_SUCCESS + if (messageData.getFarcasterUriSuccess) { + final url = messageData.getPayloadMapKey('url'); + _getFarcasterUri.complete(url); + } + // ****** CONNECT_FARCASTER_SUCCESS + if (messageData.connectFarcasterSuccess) { + _socialUsername = messageData.getPayloadMapKey('userName'); + debugPrint('[$runtimeType] connectFarcasterSuccess $_socialUsername'); + _connectFarcaster.complete(true); + } // ****** CONNECT_EMAIL if (messageData.connectEmailSuccess) { if (step.value != EmailLoginStep.loading) { @@ -447,32 +581,39 @@ class MagicService implements IMagicService { // ****** GET_USER if (messageData.getUserSuccess) { isConnected.value = true; - final data = MagicData.fromJson(messageData.payload!); + debugPrint('[$runtimeType] getUserSuccess ${messageData.payload}'); + final magicData = MagicData.fromJson(messageData.payload!).copytWith( + userName: _socialUsername, + ); + _socialUsername = null; if (!_connected.isCompleted) { final event = MagicSessionEvent( - email: data.email, - address: data.address, - chainId: data.chainId, + email: magicData.email, + userName: magicData.userName, + address: magicData.address, + chainId: magicData.chainId, + provider: magicData.provider, ); onMagicUpdate.broadcast(event); _connected.complete(isConnected.value); } else { - final session = data.copytWith( - peer: metadata, - self: ConnectionMetadata( - metadata: _metadata, - publicKey: '', + final session = magicData.copytWith( + peer: _peerMetadata.copyWith( + metadata: _peerMetadata.metadata.copyWith( + name: _socialProvider?.name ?? 'Email Wallet', + ), ), + self: _selfMetadata, + provider: _socialProvider, ); onMagicLoginSuccess.broadcast(MagicLoginEvent(session)); } } - // ****** SIGN_OUT + // ****** SIGN_OUT_SUCCESS if (messageData.signOutSuccess) { _resetTimeOut(); _disconnect.complete(true); } - // ****** SESSION_UPDATE if (messageData.sessionUpdate) { // onMagicUpdate.broadcast(MagicSessionEvent(...)); } @@ -480,29 +621,65 @@ class MagicService implements IMagicService { _error(IsConnectedErrorEvent()); } if (messageData.connectEmailError) { - String? message = messageData.payload?['message']?.toString(); + String? message = messageData.getPayloadMapKey('message'); if (message?.toLowerCase() == 'invalid params') { message = 'Wrong email format'; } _error(ConnectEmailErrorEvent(message: message)); } if (messageData.updateEmailError) { - final message = messageData.payload?['message']?.toString(); + final message = messageData.getPayloadMapKey('message'); _error(UpdateEmailErrorEvent(message: message)); } if (messageData.updateEmailPrimaryOtpError) { - final message = messageData.payload?['message']?.toString(); + final message = messageData.getPayloadMapKey('message'); _error(UpdateEmailPrimaryOtpErrorEvent(message: message)); } if (messageData.updateEmailSecondaryOtpError) { - final message = messageData.payload?['message']?.toString(); + final message = messageData.getPayloadMapKey('message'); _error(UpdateEmailSecondaryOtpErrorEvent(message: message)); } if (messageData.connectOtpError) { analyticsService.instance.sendEvent(EmailVerificationCodeFail()); - final message = messageData.payload?['message']?.toString(); + final message = messageData.getPayloadMapKey('message'); _error(ConnectOtpErrorEvent(message: message)); } + if (messageData.getSocialRedirectUriError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _getSocialRedirectUri.complete(null); + } + if (messageData.connectSocialError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _connectSocial.complete(false); + } + if (messageData.getFarcasterUriError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _getFarcasterUri.complete(null); + } + if (messageData.connectFarcasterError) { + String? message = messageData.getPayloadMapKey('message'); + message = message?.replaceFirst( + 'Error: Magic RPC Error: [-32600] ', + '', + ); + _error(MagicErrorEvent(message)); + _connectFarcaster.complete(false); + } if (messageData.getUserError) { _error(GetUserErrorEvent()); } @@ -517,7 +694,7 @@ class MagicService implements IMagicService { _error(SignOutErrorEvent()); } } catch (e, s) { - _core.logger.d('[$runtimeType] $jsMessage', stackTrace: s); + _core.logger.d('[$runtimeType] $jsMessage $e', stackTrace: s); } } @@ -566,27 +743,36 @@ class MagicService implements IMagicService { onMagicError.broadcast(errorEvent); } - Future _runJavascript() async { + Future _fitToScreen() async { return await _webViewController.runJavaScript(''' - const iframeFL = document.getElementById('frame-mobile-sdk') + if (document.querySelector('meta[name="viewport"]') === null) { + var meta = document.createElement('meta'); + meta.name = 'viewport'; + meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; + document.head.appendChild(meta); + } else { + document.querySelector('meta[name="viewport"]').setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); + } + '''); + } + Future _runJavascript() async { + return await _webViewController.runJavaScript(''' window.addEventListener('message', ({ data, origin }) => { console.log('[MagicService] received <=== ' + JSON.stringify({data,origin})) window.w3mWebview.postMessage(JSON.stringify({data,origin})) }) const sendMessage = async (message) => { + const iframeFL = document.getElementById('frame-mobile-sdk') console.log('[MagicService] posted =====> ' + JSON.stringify(message)) iframeFL.contentWindow.postMessage(message, '*') } '''); } - // sendMessage({type:"@w3m-app/GET_SOCIAL_REDIRECT_URI",payload:{provider:"x"}}); - // sendMessage({type:"@w3m-app/CONNECT_SOCIAL",payload:{uri:"https://auth.magic.link/v1/oauth2/twitter/start?magic_api_key=pk_live_B080E9DC31E5875E&magic_challenge=9HbSG6KYL3r2b7LqzhD7-EcjoHLj-a7wt7npmSBR2fw&state=FfR0W7idPzp81HM2KE~zBPR7bbSQM97CL5zZZMHd_2_ZHZ~rLvnO63MO3fd6eB4LMymif9pQupdhVL11l4NsQk4D-zQDfPGB17PpiWjPobCemCZwP.HdkH4dQeSDgkiH&platform=web&redirect_uri=https%3A%2F%2Fsecure.walletconnect.com%2Fsdk%2Foauth%3FprojectId%3Dcad4956f31a5e40a00b62865b030c6f8"}}); - void _onDebugConsoleReceived(JavaScriptConsoleMessage message) { - _core.logger.d('[$runtimeType] JS Console ${message.message}'); + _core.logger.d('[$runtimeType] JS Console: ${message.message}'); } void _onWebResourceError(WebResourceError error) { @@ -607,9 +793,9 @@ class MagicService implements IMagicService { bool _isAllowedDomain(String domain) { final domains = [ - Uri.parse(UrlConstants.secureService).authority, - Uri.parse(UrlConstants.secureDashboard).authority, - ..._safeDomains, + UrlConstants.secureOrigin1, + UrlConstants.secureOrigin2, + ..._thirdSafeDomains, ].join('|'); return RegExp(r'' + domains).hasMatch(domain); } @@ -640,9 +826,10 @@ class MagicService implements IMagicService { } if (Platform.isAndroid) { if (_webViewController.platform is AndroidWebViewController) { + final platform = + _webViewController.platform as AndroidWebViewController; AndroidWebViewController.enableDebugging(true); - (_webViewController.platform as AndroidWebViewController) - .setMediaPlaybackRequiresUserGesture(false); + platform.setMediaPlaybackRequiresUserGesture(false); final cookieManager = WebViewCookieManager().platform as AndroidWebViewCookieManager; diff --git a/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart b/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart deleted file mode 100644 index d767c7d2..00000000 --- a/packages/reown_appkit/lib/modal/services/magic_service/magic_service_singleton.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:reown_appkit/modal/services/magic_service/magic_service.dart'; - -class MagicServiceSingleton { - late MagicService instance; -} - -final magicService = MagicServiceSingleton(); diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart index c64ef98d..35d7d4e5 100644 --- a/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/frame_message.dart @@ -1,7 +1,5 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:reown_appkit/modal/constants/string_constants.dart'; import 'package:reown_appkit/modal/utils/render_utils.dart'; import 'package:reown_appkit/reown_appkit.dart'; @@ -34,8 +32,10 @@ class FrameMessage { }; bool get isValidOrigin { - return Uri.parse(origin ?? '').authority == - Uri.parse(UrlConstants.secureDashboard).authority; + final authority1 = Uri.parse(UrlConstants.secureDashboard).authority; + final authority2 = Uri.parse(UrlConstants.secureService).authority; + final originAuthority = Uri.parse(origin ?? '').authority; + return originAuthority == authority1 || originAuthority == authority2; } bool get isValidData { @@ -80,6 +80,19 @@ class MessageData { // @w3m-frame events bool get syncThemeSuccess => type == '@w3m-frame/SYNC_THEME_SUCCESS'; bool get syncDataSuccess => type == '@w3m-frame/SYNC_DAPP_DATA_SUCCESS'; + bool get getSocialRedirectUriSuccess => + type == '@w3m-frame/GET_SOCIAL_REDIRECT_URI_SUCCESS'; + bool get getSocialRedirectUriError => + type == '@w3m-frame/GET_SOCIAL_REDIRECT_URI_ERROR'; + bool get getFarcasterUriSuccess => + type == '@w3m-frame/GET_FARCASTER_URI_SUCCESS'; + bool get getFarcasterUriError => type == '@w3m-frame/GET_FARCASTER_URI_ERROR'; + bool get connectFarcasterSuccess => + type == '@w3m-frame/CONNECT_FARCASTER_SUCCESS'; + bool get connectFarcasterError => + type == '@w3m-frame/CONNECT_FARCASTER_ERROR'; + bool get connectSocialSuccess => type == '@w3m-frame/CONNECT_SOCIAL_SUCCESS'; + bool get connectSocialError => type == '@w3m-frame/CONNECT_SOCIAL_ERROR'; bool get connectEmailSuccess => type == '@w3m-frame/CONNECT_EMAIL_SUCCESS'; bool get connectEmailError => type == '@w3m-frame/CONNECT_EMAIL_ERROR'; bool get updateEmailSuccess => type == '@w3m-frame/UPDATE_EMAIL_SUCCESS'; @@ -124,15 +137,59 @@ class SwitchNetwork extends MessageData { }) : super(type: '@w3m-app/SWITCH_NETWORK'); @override - String toString() => '{type:\'${super.type}\',payload:{chainId:$chainId}}'; + String toString() => '{type:"${super.type}",payload:{chainId:$chainId}}'; } +class GetSocialRedirectUri extends MessageData { + final String provider; + final String? schema; + GetSocialRedirectUri({ + required this.provider, + this.schema, + }) : super(type: '@w3m-app/GET_SOCIAL_REDIRECT_URI'); + + @override + String toString() { + final p = 'provider:"$provider"'; + final s = 'schema:"$schema"'; + + if (schema != null) { + return '{type:"${super.type}",payload:{$p,$s}}'; + } + return '{type:"${super.type}",payload:{$p}}'; + } +} + +class ConnectSocial extends MessageData { + final String uri; + ConnectSocial({ + required this.uri, + }) : super(type: '@w3m-app/CONNECT_SOCIAL'); + + @override + String toString() => '{type:"${super.type}",payload:{uri:"$uri"}}'; +} + +class GetFarcasterUri extends MessageData { + GetFarcasterUri() : super(type: '@w3m-app/GET_FARCASTER_URI'); + + @override + String toString() => '{type:"${super.type}"}'; +} + +// class ConnectFarcaster extends MessageData { +// ConnectFarcaster() : super(type: '@w3m-app/CONNECT_FARCASTER'); + +// @override +// String toString() => '{type:"${super.type}"}'; +// } + class ConnectEmail extends MessageData { final String email; ConnectEmail({required this.email}) : super(type: '@w3m-app/CONNECT_EMAIL'); @override - String toString() => '{type:\'${super.type}\',payload:{email:\'$email\'}}'; + String toString() => '{type:"${super.type}",payload:{email:"$email"}}'; } class UpdateEmail extends MessageData { @@ -140,7 +197,7 @@ class UpdateEmail extends MessageData { UpdateEmail({required this.email}) : super(type: '@w3m-app/UPDATE_EMAIL'); @override - String toString() => '{type:\'${super.type}\',payload:{email:\'$email\'}}'; + String toString() => '{type:"${super.type}",payload:{email:"$email"}}'; } class UpdateEmailPrimaryOtp extends MessageData { @@ -150,7 +207,7 @@ class UpdateEmailPrimaryOtp extends MessageData { }) : super(type: '@w3m-app/UPDATE_EMAIL_PRIMARY_OTP'); @override - String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; + String toString() => '{type:"${super.type}",payload:{otp:"$otp"}}'; } class UpdateEmailSecondaryOtp extends MessageData { @@ -160,7 +217,7 @@ class UpdateEmailSecondaryOtp extends MessageData { }) : super(type: '@w3m-app/UPDATE_EMAIL_SECONDARY_OTP'); @override - String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; + String toString() => '{type:"${super.type}",payload:{otp:"$otp"}}'; } class ConnectOtp extends MessageData { @@ -168,7 +225,7 @@ class ConnectOtp extends MessageData { ConnectOtp({required this.otp}) : super(type: '@w3m-app/CONNECT_OTP'); @override - String toString() => '{type:\'${super.type}\',payload:{otp:\'$otp\'}}'; + String toString() => '{type:"${super.type}",payload:{otp:"$otp"}}'; } class GetUser extends MessageData { @@ -178,9 +235,9 @@ class GetUser extends MessageData { @override String toString() { if ((chainId ?? '').isNotEmpty) { - return '{type:\'${super.type}\',payload:{chainId:$chainId}}'; + return '{type:"${super.type}",payload:{chainId:$chainId}}'; } - return '{type:\'${super.type}\'}'; + return '{type:"${super.type}"}'; } } @@ -209,23 +266,26 @@ class RpcRequest extends MessageData { @override String toString() { - debugPrint('[$runtimeType] method $method'); - final m = 'method:\'$method\''; - final t = 'type:\'${super.type}\''; + final m = 'method:"$method"'; + final t = 'type:"${super.type}"'; final p = params.map((i) => '$i').toList(); if (method == 'personal_sign') { final data = p.first; final address = p.last; - return '{$t,payload:{$m,params:[\'$data\',\'$address\']}}'; + return '{$t,payload:{$m,params:["$data","$address"]}}'; + } + if (method == 'eth_sign') { + final address = p.first; + final data = p.last; + return '{$t,payload:{$m,params:["$address","$data"]}}'; } if (method == 'eth_signTypedData_v4' || method == 'eth_signTypedData_v3' || method == 'eth_signTypedData') { - // final data = jsonEncode(jsonDecode(p.first) as Map); final data = p.first; final address = p.last; - return '{$t,payload:{$m,params:[\'$address\',\'$data\']}}'; + return '{$t,payload:{$m,params:["$address","$data"]}}'; } if (method == 'eth_sendTransaction' || method == 'eth_signTransaction') { final jp = jsonEncode(params.first); @@ -252,20 +312,20 @@ class SyncTheme extends MessageData { colors = themeData.lightColors; } - final tm = 'themeMode:\'$mode\''; + final tm = 'themeMode:"$mode"'; final mix = RenderUtils.colorToRGBA(colors.background125); - final tv1 = '\'--w3m-color-mix\':\'$mix\''; - // final tv2 = '\'--w3m-color-mix-strength\':\'0%\''; + final tv1 = '"--w3m-color-mix":"$mix"'; + // final tv2 = '"--w3m-color-mix-strength":"0%"'; final tv = 'themeVariables:{$tv1}'; final accent = RenderUtils.colorToRGBA(colors.accent100); - final wtv1 = '\'--w3m-accent\':\'$accent\''; + final wtv1 = '"--w3m-accent":"$accent"'; final background = RenderUtils.colorToRGBA(colors.background125); - final wtv2 = '\'--w3m-background\':\'$background\''; + final wtv2 = '"--w3m-background":"$background"'; final w3mtv = 'w3mThemeVariables:{$wtv1,$wtv2}'; - return '{type:\'${super.type}\',payload:{$tm, $tv,$w3mtv}}'; + return '{type:"${super.type}",payload:{$tm, $tv,$w3mtv}}'; } } @@ -283,14 +343,18 @@ class SyncAppData extends MessageData { @override String toString() { final v = 'verified: true'; - final p1 = 'projectId:\'$projectId\''; - final p2 = 'sdkVersion:\'$sdkVersion\''; - final m1 = 'name:\'${metadata.name}\''; - final m2 = 'description:\'${metadata.description}\''; - final m3 = 'url:\'${metadata.url}\''; + final p1 = 'projectId:"$projectId"'; + final p2 = 'sdkVersion:"$sdkVersion"'; + final m1 = 'name:"${metadata.name}"'; + final m2 = 'description:"${metadata.description}"'; + final m3 = 'url:"${metadata.url}"'; final m4 = 'icons:["${metadata.icons.first}"]'; - final p3 = 'metadata:{$m1,$m2,$m3,$m4}'; + final r1 = 'native:"${metadata.redirect?.native}"'; + final r2 = 'universal:"${metadata.redirect?.universal}"'; + final r3 = 'linkMode:"${metadata.redirect?.linkMode}"'; + final m5 = 'redirect:{$r1,$r2,$r3}'; + final p3 = 'metadata:{$m1,$m2,$m3,$m4,$m5}'; final p = 'payload:{$v,$p1,$p2,$p3}'; - return '{type:\'${super.type}\',$p}'; + return '{type:"${super.type}",$p}'; } } diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart index 3237809b..8acd35d4 100644 --- a/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_data.dart @@ -1,31 +1,45 @@ import 'package:reown_appkit/reown_appkit.dart'; class MagicData { - String email; + String? email; String address; int chainId; + String? userName; + bool? smartAccountDeployed; + String? preferredAccountType; ConnectionMetadata? self; ConnectionMetadata? peer; + AppKitSocialOption? provider; MagicData({ required this.email, required this.chainId, required this.address, + this.userName, + this.smartAccountDeployed, + this.preferredAccountType, this.self, this.peer, + this.provider, }); factory MagicData.fromJson(Map json) { return MagicData( - email: json['email'].toString(), + email: json['email']?.toString(), address: json['address'].toString(), chainId: int.parse(json['chainId'].toString()), + userName: json['userName']?.toString(), + smartAccountDeployed: json['smartAccountDeployed'] as bool?, + preferredAccountType: json['preferredAccountType']?.toString(), self: (json['self'] != null) ? ConnectionMetadata.fromJson(json['self']) : null, peer: (json['peer'] != null) ? ConnectionMetadata.fromJson(json['peer']) : null, + provider: (json['provider'] != null) + ? AppKitSocialOption.fromString(json['provider'].toString()) + : null, ); } @@ -34,8 +48,12 @@ class MagicData { 'email': email, 'address': address, 'chainId': chainId, + 'userName': userName, + 'smartAccountDeployed': smartAccountDeployed, + 'preferredAccountType': preferredAccountType, 'self': self?.toJson(), 'peer': peer?.toJson(), + 'provider': provider?.name, }; } @@ -46,15 +64,23 @@ class MagicData { String? email, String? address, int? chainId, + String? userName, + bool? smartAccountDeployed, + String? preferredAccountType, ConnectionMetadata? self, ConnectionMetadata? peer, + AppKitSocialOption? provider, }) { return MagicData( email: email ?? this.email, address: address ?? this.address, chainId: chainId ?? this.chainId, + userName: userName ?? this.userName, + smartAccountDeployed: smartAccountDeployed ?? this.smartAccountDeployed, + preferredAccountType: preferredAccountType ?? this.preferredAccountType, self: self ?? this.self, peer: peer ?? this.peer, + provider: provider ?? this.provider, ); } } diff --git a/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart index 2e812f1f..ff8e4f68 100644 --- a/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart +++ b/packages/reown_appkit/lib/modal/services/magic_service/models/magic_events.dart @@ -11,11 +11,15 @@ class MagicLoginEvent implements EventArgs { class MagicSessionEvent implements EventArgs { String? email; + String? userName; + AppKitSocialOption? provider; String? address; int? chainId; MagicSessionEvent({ this.email, + this.userName, + this.provider, this.address, this.chainId, }); @@ -25,6 +29,12 @@ class MagicSessionEvent implements EventArgs { if ((email ?? '').isNotEmpty) { params['email'] = email; } + if ((userName ?? '').isNotEmpty) { + params['userName'] = userName; + } + if (provider != null) { + params['provider'] = provider; + } if ((address ?? '').isNotEmpty) { params['address'] = address; } @@ -122,3 +132,8 @@ class RpcRequestErrorEvent extends MagicErrorEvent { RpcRequestErrorEvent(String? message) : super(message ?? 'Error during request'); } + +class CompleteSocialLoginEvent implements EventArgs { + final String url; + CompleteSocialLoginEvent(this.url); +} diff --git a/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart index 0241ff24..402c23d3 100644 --- a/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart +++ b/packages/reown_appkit/lib/modal/services/siwe_service/siwe_service.dart @@ -1,13 +1,16 @@ import 'dart:convert'; import 'package:convert/convert.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/constants/string_constants.dart'; import 'package:reown_appkit/modal/services/coinbase_service/coinbase_service_singleton.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/siwe_service/i_siwe_service.dart'; import 'package:reown_appkit/reown_appkit.dart'; class SiweService implements ISiweService { + IMagicService get _magicService => GetIt.I(); + late final SIWEConfig? _siweConfig; final IReownAppKit _appKit; @@ -88,7 +91,7 @@ class SiweService implements ISiweService { final encoded = hex.encode(bytes); // if (session.sessionService.isMagic) { - return await magicService.instance.request( + return await _magicService.request( chainId: caip2Chain, request: SessionRequestParams( method: 'personal_sign', diff --git a/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart b/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart index dc6486b8..2a5c745d 100644 --- a/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart +++ b/packages/reown_appkit/lib/modal/services/uri_service/i_url_utils.dart @@ -1,5 +1,4 @@ import 'package:reown_appkit/modal/utils/platform_utils.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; abstract class IUriService { @@ -7,7 +6,7 @@ abstract class IUriService { Future isInstalled(String? uri, {String? id}); - Future launchUrl(Uri url, {LaunchMode? mode}); + Future launchUrl(Uri url, {dynamic mode}); Future openRedirect( WalletRedirect redirect, { diff --git a/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart b/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart index 6f64b0de..8c2845ad 100644 --- a/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart +++ b/packages/reown_appkit/lib/modal/services/uri_service/url_utils.dart @@ -4,7 +4,6 @@ import 'package:reown_appkit/modal/services/uri_service/launch_url_exception.dar import 'package:reown_appkit/modal/utils/core_utils.dart'; import 'package:reown_appkit/modal/utils/platform_utils.dart'; import 'package:reown_appkit/reown_appkit.dart'; -import 'package:url_launcher/url_launcher.dart' as launcher; import 'package:reown_appkit/modal/services/explorer_service/models/redirect.dart'; class UriService extends IUriService { @@ -28,13 +27,13 @@ class UriService extends IUriService { if (p == PlatformExact.android) { return await _androidAppCheck(uri); } else if (p == PlatformExact.ios) { - return await launcher.canLaunchUrl(Uri.parse(uri)); + return await ReownCoreUtils.canOpenUrl(Uri.parse(uri).toString()); } } on FormatException catch (e) { if (id != null) { - _core.logger.d('[$runtimeType] $uri ($id): ${e.message}'); + _core.logger.i('[$runtimeType] $uri ($id): ${e.message}'); } else { - _core.logger.d('[$runtimeType] $uri: ${e.message}'); + _core.logger.i('[$runtimeType] $uri: ${e.message}'); } } catch (e) { rethrow; @@ -45,11 +44,8 @@ class UriService extends IUriService { } @override - Future launchUrl(Uri url, {launcher.LaunchMode? mode}) async { - return await _launchUrlFunc( - url, - mode: mode, - ); + Future launchUrl(Uri url, {dynamic mode}) async { + return await _launchUrlFunc(url); } @override @@ -89,18 +85,12 @@ class UriService extends IUriService { return false; } _core.logger.i('[$runtimeType] openRedirect $uriToOpen'); - return await _launchUrlFunc( - uriToOpen!, - mode: launcher.LaunchMode.externalApplication, - ); + return await _launchUrlFunc(uriToOpen!); } - Future _launchUrlFunc(Uri url, {launcher.LaunchMode? mode}) async { + Future _launchUrlFunc(Uri url) async { try { - final success = await launcher.launchUrl( - url, - mode: mode ?? launcher.LaunchMode.platformDefault, - ); + final success = await ReownCoreUtils.openURL(url.toString()); if (!success) { throw CanNotLaunchUrl(); } diff --git a/packages/reown_appkit/lib/modal/utils/core_utils.dart b/packages/reown_appkit/lib/modal/utils/core_utils.dart index d32abfe4..06738494 100644 --- a/packages/reown_appkit/lib/modal/utils/core_utils.dart +++ b/packages/reown_appkit/lib/modal/utils/core_utils.dart @@ -7,8 +7,8 @@ class CoreUtils { } static bool isValidEmail(String email) { - return RegExp( - r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + if (email.contains(' ')) return false; + return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$') .hasMatch(email); } diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/email_login_input_field.dart b/packages/reown_appkit/lib/modal/widgets/buttons/email_login_input_field.dart new file mode 100644 index 00000000..ebed2631 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/email_login_input_field.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; +import 'package:reown_appkit/modal/services/analytics_service/analytics_service_singleton.dart'; +import 'package:reown_appkit/modal/services/analytics_service/models/analytics_event.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/email_login_step.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/input_email.dart'; +import 'package:reown_appkit/modal/widgets/modal_provider.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; + +class EmailLoginInputField extends StatefulWidget { + const EmailLoginInputField({super.key}); + + @override + State createState() => _EmailLoginInputFieldState(); +} + +class _EmailLoginInputFieldState extends State { + IMagicService get _magicService => GetIt.I(); + + bool _submitted = false; + @override + void initState() { + super.initState(); + _magicService.step.addListener(_stepListener); + } + + void _stepListener() { + if ((_magicService.step.value == EmailLoginStep.verifyDevice || + _magicService.step.value == EmailLoginStep.verifyOtp || + _magicService.step.value == EmailLoginStep.verifyOtp2) && + _submitted) { + widgetStack.instance.push(ConfirmEmailPage()); + _submitted = false; + } + } + + @override + void dispose() { + _magicService.step.removeListener(_stepListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _magicService.isEmailEnabled, + builder: (context, emailEnabled, _) { + if (!emailEnabled) { + return const SizedBox.shrink(); + } + return InputEmailWidget( + onFocus: (value) { + if (value) { + analyticsService.instance.sendEvent(EmailLoginSelected()); + } + }, + onValueChange: (value) { + _magicService.setEmail(value); + }, + onSubmitted: (value) { + setState(() => _submitted = true); + final service = ModalProvider.of(context).instance; + final chainId = service.selectedChain?.chainId; + analyticsService.instance.sendEvent(EmailSubmitted()); + _magicService.connectEmail( + value: value, + chainId: chainId, + ); + }, + ); + }, + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart index ba7a5483..db6c58c2 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/buttons/primary_button.dart @@ -6,11 +6,15 @@ class PrimaryButton extends StatelessWidget { final String title; final VoidCallback? onTap; final bool loading; + final Color? color; + final BorderRadiusGeometry? borderRadius; const PrimaryButton({ super.key, required this.title, this.onTap, this.loading = false, + this.color, + this.borderRadius, }); @override @@ -36,7 +40,7 @@ class PrimaryButton extends StatelessWidget { if (states.contains(MaterialState.disabled)) { return themeColors.grayGlass010; } - return themeColors.accent100; + return color ?? themeColors.accent100; }, ), foregroundColor: MaterialStateProperty.resolveWith( @@ -54,9 +58,10 @@ class PrimaryButton extends StatelessWidget { color: themeColors.grayGlass010, width: 1.0, ), - borderRadius: radiuses.isSquare() - ? BorderRadius.all(Radius.zero) - : BorderRadius.circular(16.0), + borderRadius: borderRadius ?? + (radiuses.isSquare() + ? BorderRadius.all(Radius.zero) + : BorderRadius.circular(16.0)), ); }, ), diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/social_login_button.dart b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_button.dart new file mode 100644 index 00000000..83e87b2b --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_button.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; +import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; +import 'package:shimmer/shimmer.dart'; + +class SocialLoginButton extends StatelessWidget { + const SocialLoginButton({ + super.key, + required this.logoPath, + required this.onTap, + this.title, + this.textAlign = TextAlign.center, + }); + final String logoPath; + final VoidCallback onTap; + final String? title; + final TextAlign textAlign; + + @override + Widget build(BuildContext context) { + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final themeData = ReownAppKitModalTheme.getDataOf(context); + final themeColors = ReownAppKitModalTheme.colorsOf(context); + return BaseListItem( + onTap: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LayoutBuilder(builder: (_, constraints) { + return ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(constraints.maxHeight), + child: SvgPicture.asset( + logoPath, + package: 'reown_appkit', + height: constraints.maxHeight, + width: constraints.maxHeight, + ), + ); + }), + if (title != null) + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + right: kListItemHeight - 12.0, + ), + child: Text( + title!, + textAlign: textAlign, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + ], + ), + ); + } +} + +class ShimmerSocialLoginButton extends StatelessWidget { + const ShimmerSocialLoginButton({super.key, this.title}); + final String? title; + + @override + Widget build(BuildContext context) { + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final themeData = ReownAppKitModalTheme.getDataOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + return Shimmer.fromColors( + baseColor: themeColors.grayGlass005, + highlightColor: themeColors.grayGlass020, + child: BaseListItem( + child: Row( + children: [ + LayoutBuilder(builder: (_, constraints) { + return ClipRRect( + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : BorderRadius.circular(constraints.maxHeight), + child: Container( + width: constraints.maxHeight, + height: constraints.maxHeight, + color: Colors.black.withOpacity(0.2), + ), + ); + }), + if (title != null) + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: kListItemHeight - 12.0), + child: Text( + title!, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/social_login_buttons_view.dart b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_buttons_view.dart new file mode 100644 index 00000000..fca0e2fe --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/buttons/social_login_buttons_view.dart @@ -0,0 +1,161 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/pages/all_social_logins.dart'; +import 'package:reown_appkit/modal/pages/social_login_page.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/utils/asset_util.dart'; +import 'package:reown_appkit/modal/widgets/buttons/social_login_button.dart'; +import 'package:reown_appkit/modal/widgets/miscellaneous/responsive_container.dart'; +import 'package:reown_appkit/modal/widgets/widget_stack/widget_stack_singleton.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class SocialLoginButtonsView extends StatefulWidget { + const SocialLoginButtonsView({super.key}); + + @override + State createState() => _SocialLoginButtonsViewState(); +} + +class _SocialLoginButtonsViewState extends State { + IMagicService get _magicService => GetIt.I(); + + @override + Widget build(BuildContext context) { + final isPortrait = ResponsiveData.isPortrait(context); + return ValueListenableBuilder( + valueListenable: _magicService.isReady, + builder: (context, isReady, _) { + final options = _magicService.socials; + final count = options.length; + if (count == 0) { + return SizedBox.shrink(); + } + if (count == 1) { + return Column( + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${options.first.name.toLowerCase()}_logo.svg', + ), + onTap: () => _initSocialLogin(options.first), + title: 'Continue with ${options.first.name}', + ) + : ShimmerSocialLoginButton( + title: 'Continue with ${options.first.name}', + ), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ], + ); + } + final maxItems = isPortrait ? 6 : 8; + final isLess = count <= 4; + final fits = count == maxItems; + final exceeds = count > maxItems; + // + final firstItem = isLess ? null : options.first; + final restItems = isLess + ? options + : fits + ? options.sublist(1, min(options.length, maxItems)) + : options.sublist(1, min(options.length, (maxItems - 1))); + // + final secondRowList = [ + ...restItems.map( + (item) => Expanded( + child: isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${item.name.toLowerCase()}_logo.svg', + ), + onTap: () => _initSocialLogin(item), + ) + : ShimmerSocialLoginButton(), + ), + ), + if (exceeds) + Expanded( + child: isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + 'more_social_icon.svg', + ), + onTap: () { + widgetStack.instance.push( + AllSocialLoginsPage( + onSelect: (selected) { + widgetStack.instance.pop(); + _initSocialLogin(selected); + }, + ), + ); + }, + ) + : ShimmerSocialLoginButton(), + ), + ]; + return Column( + children: [ + Builder( + builder: (_) { + if (firstItem == null) { + return SizedBox.shrink(); + } + return Column( + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + isReady + ? SocialLoginButton( + logoPath: AssetUtils.getThemedAsset( + context, + '${firstItem.name.toLowerCase()}_logo.svg', + ), + onTap: () => _initSocialLogin(firstItem), + title: 'Continue with ${firstItem.name}', + ) + : ShimmerSocialLoginButton( + title: 'Continue with ${firstItem.name}', + ), + ], + ); + }, + ), + Column( + children: [ + const SizedBox.square(dimension: kListViewSeparatorHeight), + Row( + children: _buttonsWithDivider(secondRowList), + ), + ], + ), + const SizedBox.square(dimension: kListViewSeparatorHeight), + ], + ); + }, + ); + } + + void _initSocialLogin(AppKitSocialOption option) => widgetStack.instance.push( + SocialLoginPage(socialOption: option), + ); + + List _buttonsWithDivider(List widgets) { + List spacedWidgets = []; + for (int i = 0; i < widgets.length; i++) { + spacedWidgets.add(widgets[i]); + if (i < widgets.length - 1) { + spacedWidgets.add( + const SizedBox.square(dimension: kListViewSeparatorHeight), + ); + } + } + return spacedWidgets; + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart index 4abbc06d..6e02ecfe 100644 --- a/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/all_wallets_item.dart @@ -8,10 +8,14 @@ class AllWalletsItem extends StatelessWidget { const AllWalletsItem({ super.key, this.title = 'All wallets', + this.titleAlign = TextAlign.left, + this.leading, this.trailing, this.onTap, }); final String title; + final TextAlign titleAlign; + final Widget? leading; final Widget? trailing; final VoidCallback? onTap; @@ -21,30 +25,36 @@ class AllWalletsItem extends StatelessWidget { final themeColors = ReownAppKitModalTheme.colorsOf(context); return BaseListItem( onTap: onTap, - child: Row( - children: [ - LayoutBuilder( - builder: (_, constraints) { - return ThemedIcon( - iconPath: 'lib/modal/assets/icons/dots.svg', - size: constraints.maxHeight, - ); - }, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Text( - title, - style: themeData.textStyles.paragraph500.copyWith( - color: themeColors.foreground100, + child: LayoutBuilder(builder: (_, constraints) { + return Row( + children: [ + SizedBox.square( + dimension: constraints.maxHeight, + child: leading ?? + ThemedIcon( + iconPath: 'lib/modal/assets/icons/dots.svg', + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + title, + textAlign: titleAlign, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground100, + ), ), ), ), - ), - trailing ?? const SizedBox.shrink(), - ], - ), + SizedBox.square( + dimension: constraints.maxHeight, + child: Container(), + ), + ], + ); + }), + trailing: trailing, ); } } diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart index faad6997..1e244e84 100644 --- a/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/base_list_item.dart @@ -53,7 +53,12 @@ class BaseListItem extends StatelessWidget { ), child: Padding( padding: padding ?? const EdgeInsets.all(8.0), - child: child, + child: Row( + children: [ + Expanded(child: child), + trailing ?? SizedBox.shrink(), + ], + ), ), ); } diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart index 17a56eef..a85c536e 100644 --- a/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart +++ b/packages/reown_appkit/lib/modal/widgets/lists/list_items/download_wallet_item.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:reown_appkit/reown_appkit.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:reown_appkit/modal/widgets/buttons/simple_icon_button.dart'; import 'package:reown_appkit/modal/widgets/modal_provider.dart'; @@ -54,10 +53,7 @@ class DownloadWalletItem extends StatelessWidget { void _downloadApp(BuildContext context) { try { - launchUrlString( - _storeUrl, - mode: LaunchMode.externalApplication, - ); + ReownCoreUtils.openURL(_storeUrl); } catch (e) { ModalProvider.of(context).instance.connectSelectedWallet(); } diff --git a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart b/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart deleted file mode 100644 index 370ec6b8..00000000 --- a/packages/reown_appkit/lib/modal/widgets/lists/list_items/wallet_connect_item.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; - -import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; -import 'package:reown_appkit/modal/utils/asset_util.dart'; -import 'package:reown_appkit/modal/widgets/lists/list_items/base_list_item.dart'; -import 'package:reown_appkit/modal/widgets/lists/list_items/wallet_item_chip.dart'; - -class WalletConnectItem extends StatelessWidget { - const WalletConnectItem({ - super.key, - this.onTap, - }); - final VoidCallback? onTap; - - @override - Widget build(BuildContext context) { - final themeData = ReownAppKitModalTheme.getDataOf(context); - final themeColors = ReownAppKitModalTheme.colorsOf(context); - return BaseListItem( - onTap: onTap, - child: Row( - children: [ - LayoutBuilder( - builder: (context, constraints) { - return SvgPicture.asset( - AssetUtils.getThemedAsset(context, 'logo_walletconnect.svg'), - package: 'reown_appkit', - height: constraints.maxHeight, - width: constraints.maxHeight, - ); - }, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Text( - 'WalletConnect', - style: themeData.textStyles.paragraph500.copyWith( - color: themeColors.foreground100, - ), - ), - ), - ), - WalletItemChip( - value: ' QR CODE ', - color: themeColors.accenGlass015, - textStyle: themeData.textStyles.micro700.copyWith( - color: themeColors.accent100, - ), - ), - ], - ), - ); - } -} diff --git a/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart b/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart index 0b474ac1..e90adfe3 100644 --- a/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart +++ b/packages/reown_appkit/lib/modal/widgets/lists/wallets_list.dart @@ -40,16 +40,24 @@ class WalletsList extends StatelessWidget { ); final walletsListItems = isLoading - ? loadingList + ? loadingList.map( + (listItem) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: listItem, + ), + ) : itemList.map( - (e) => WalletListItem( - onTap: () => onTapWallet?.call(e.data), - showCheckmark: e.data.installed, - imageUrl: e.image, - title: e.title, - trailing: e.data.recent - ? const WalletItemChip(value: ' RECENT ') - : null, + (listItem) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: WalletListItem( + onTap: () => onTapWallet?.call(listItem.data), + showCheckmark: listItem.data.installed, + imageUrl: listItem.image, + title: listItem.title, + trailing: listItem.data.recent + ? const WalletItemChip(value: ' RECENT ') + : null, + ), ), ); final List items = List.from(walletsListItems); @@ -62,7 +70,7 @@ class WalletsList extends StatelessWidget { return ListView.separated( padding: const EdgeInsets.symmetric( - horizontal: kPadding12, + horizontal: kPadding8, vertical: kPadding12, ), itemBuilder: (context, index) { @@ -72,7 +80,7 @@ class WalletsList extends StatelessWidget { ); }, separatorBuilder: (_, index) => SizedBox.square( - dimension: index == 0 ? 0.0 : kListViewSeparatorHeight, + dimension: kListViewSeparatorHeight, ), itemCount: items.length, ); diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart index e01cbe2e..016e9a9c 100644 --- a/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/input_email.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/constants/style_constants.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; +import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; +import 'package:reown_appkit/modal/services/toast_service/models/toast_message.dart'; +import 'package:reown_appkit/modal/services/toast_service/toast_service_singleton.dart'; import 'package:reown_appkit/modal/utils/core_utils.dart'; import 'package:reown_appkit/modal/widgets/circular_loader.dart'; import 'package:reown_appkit/modal/widgets/miscellaneous/searchbar.dart'; @@ -26,8 +31,10 @@ class InputEmailWidget extends StatefulWidget { } class _InputEmailWidgetState extends State { - bool hasFocus = false; + IMagicService get _magicService => GetIt.I(); + late TextEditingController _controller; + bool hasFocus = false; bool _ready = false; bool _timedOut = false; bool _submitted = false; @@ -36,19 +43,29 @@ class _InputEmailWidgetState extends State { void initState() { super.initState(); _controller = TextEditingController(text: widget.initialValue); - _ready = magicService.instance.isReady.value; - _timedOut = magicService.instance.isTimeout.value; - magicService.instance.isReady.addListener(_updateStatus); - magicService.instance.isTimeout.addListener(_updateStatus); + _ready = _magicService.isReady.value; + _timedOut = _magicService.isTimeout.value; + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.isReady.addListener(_updateStatus); + _magicService.isTimeout.addListener(_updateStatus); } void _updateStatus() { setState(() { - _ready = magicService.instance.isReady.value; - _timedOut = magicService.instance.isTimeout.value; + _ready = _magicService.isReady.value; + _timedOut = _magicService.isTimeout.value; }); } + void _onMagicErrorEvent(MagicErrorEvent? event) { + toastService.instance.show(ToastMessage( + type: ToastType.error, + text: event?.error ?? 'An error occurred.', + )); + _submitted = false; + setState(() {}); + } + @override void didUpdateWidget(covariant InputEmailWidget oldWidget) { _updateStatus(); @@ -57,8 +74,9 @@ class _InputEmailWidgetState extends State { @override void dispose() { - magicService.instance.isTimeout.addListener(_updateStatus); - magicService.instance.isReady.removeListener(_updateStatus); + _magicService.onMagicError.subscribe(_onMagicErrorEvent); + _magicService.isTimeout.addListener(_updateStatus); + _magicService.isReady.removeListener(_updateStatus); super.dispose(); } @@ -66,6 +84,7 @@ class _InputEmailWidgetState extends State { Widget build(BuildContext context) { final themeColors = ReownAppKitModalTheme.colorsOf(context); return ModalSearchBar( + height: kListItemHeight, enabled: !_timedOut && _ready && !_submitted, controller: _controller, initialValue: _controller.text, @@ -80,16 +99,23 @@ class _InputEmailWidgetState extends State { }, onFocusChange: _onFocusChange, suffixIcon: widget.suffixIcon ?? - (!magicService.instance.isReady.value || _submitted + (!_magicService.isReady.value || _submitted ? Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - CircularLoader(size: 20.0, strokeWidth: 2.0), + Row( + children: [ + CircularLoader(size: 20.0, strokeWidth: 2.0), + const SizedBox.square( + dimension: kListViewSeparatorHeight, + ), + ], + ) ], ) : ValueListenableBuilder( - valueListenable: magicService.instance.email, + valueListenable: _magicService.email, builder: (context, value, _) { if (!hasFocus || _invalidEmail(value)) { return SizedBox.shrink(); @@ -136,8 +162,8 @@ class _InputEmailWidgetState extends State { void _clearEmail() { _controller.clear(); - magicService.instance.setEmail(''); - magicService.instance.setNewEmail(''); + _magicService.setEmail(''); + _magicService.setNewEmail(''); FocusManager.instance.primaryFocus?.unfocus(); } } diff --git a/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart b/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart index 1c1d6223..d5a8d474 100644 --- a/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart +++ b/packages/reown_appkit/lib/modal/widgets/miscellaneous/searchbar.dart @@ -29,6 +29,7 @@ class ModalSearchBar extends StatefulWidget { this.debounce = true, this.focusNode, this.width, + this.height = kSearchFieldHeight, this.enabled = true, this.inputFormatters, }); @@ -52,6 +53,7 @@ class ModalSearchBar extends StatefulWidget { final bool debounce; final FocusNode? focusNode; final double? width; + final double height; final bool enabled; final List? inputFormatters; @@ -113,7 +115,11 @@ class _ModalSearchBarState extends State final radiuses = ReownAppKitModalTheme.radiusesOf(context); _decorationTween = DecorationTween( begin: BoxDecoration( - borderRadius: BorderRadius.circular(radiuses.radiusXS), + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : (radiuses.isCircular() + ? BorderRadius.circular(widget.height) + : BorderRadius.circular(widget.height * 0.4)), boxShadow: [ BoxShadow( color: Colors.transparent, @@ -125,7 +131,11 @@ class _ModalSearchBarState extends State ], ), end: BoxDecoration( - borderRadius: BorderRadius.circular(radiuses.radiusXS), + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : (radiuses.isCircular() + ? BorderRadius.circular(widget.height) + : BorderRadius.circular(widget.height * 0.4)), boxShadow: [ BoxShadow( color: themeColors.accenGlass015, @@ -175,7 +185,11 @@ class _ModalSearchBarState extends State final radiuses = ReownAppKitModalTheme.radiusesOf(context); final unfocusedBorder = OutlineInputBorder( borderSide: BorderSide(color: themeColors.grayGlass005, width: 1.0), - borderRadius: BorderRadius.circular(radiuses.radius2XS), + borderRadius: radiuses.isSquare() + ? BorderRadius.zero + : (radiuses.isCircular() + ? BorderRadius.circular(widget.height) + : BorderRadius.circular(widget.height * 0.3)), ); final focusedBorder = unfocusedBorder.copyWith( borderSide: BorderSide(color: themeColors.accent100, width: 1.0), @@ -187,7 +201,7 @@ class _ModalSearchBarState extends State return DecoratedBoxTransition( decoration: _decorationTween.animate(_animationController), child: Container( - height: kSearchFieldHeight + 8.0, + height: widget.height + 8.0, width: widget.width, padding: const EdgeInsets.all(4.0), child: TextFormField( @@ -228,7 +242,7 @@ class _ModalSearchBarState extends State Container( width: 16.0, height: 16.0, - margin: const EdgeInsets.only(left: kPadding12), + margin: const EdgeInsets.only(left: kPadding16), child: GestureDetector( onTap: () { _controller.clear(); @@ -249,10 +263,10 @@ class _ModalSearchBarState extends State ], ), prefixIconConstraints: BoxConstraints( - maxHeight: kSearchFieldHeight, - minHeight: kSearchFieldHeight, - maxWidth: 36.0, - minWidth: widget.noIcons ? 0.0 : 36.0, + maxHeight: widget.height, + minHeight: widget.height, + maxWidth: 40.0, + minWidth: widget.noIcons ? 0.0 : 40.0, ), labelStyle: themeData.textStyles.paragraph500.copyWith( color: themeColors.inverse100, @@ -292,8 +306,8 @@ class _ModalSearchBarState extends State ) : null), suffixIconConstraints: BoxConstraints( - maxHeight: kSearchFieldHeight, - minHeight: kSearchFieldHeight, + maxHeight: widget.height, + minHeight: widget.height, maxWidth: 36.0, minWidth: widget.noIcons ? 0.0 : 36.0, ), @@ -303,7 +317,7 @@ class _ModalSearchBarState extends State disabledBorder: disabledBorder, focusedBorder: focusedBorder, filled: true, - fillColor: themeColors.grayGlass005, + fillColor: themeColors.grayGlass002, contentPadding: const EdgeInsets.all(0.0), ), ), diff --git a/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart b/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart index bde44137..817f58fd 100644 --- a/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart +++ b/packages/reown_appkit/lib/modal/widgets/navigation/navbar.dart @@ -14,10 +14,12 @@ class ModalNavbar extends StatelessWidget { required this.body, required this.title, this.leftAction, + this.rightAction, this.safeAreaLeft = false, this.safeAreaRight = false, this.safeAreaBottom = true, this.noClose = false, + this.noBack = false, this.divider = true, }); @@ -26,7 +28,13 @@ class ModalNavbar extends StatelessWidget { final Widget body; final String title; final NavbarActionButton? leftAction; - final bool safeAreaLeft, safeAreaRight, safeAreaBottom, noClose, divider; + final NavbarActionButton? rightAction; + final bool safeAreaLeft, + safeAreaRight, + safeAreaBottom, + noClose, + noBack, + divider; @override Widget build(BuildContext context) { @@ -54,7 +62,7 @@ class ModalNavbar extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - widgetStack.instance.canPop() + widgetStack.instance.canPop() && !noBack ? NavbarActionButton( asset: 'lib/modal/assets/icons/chevron_left.svg', action: onBack ?? widgetStack.instance.pop, @@ -82,6 +90,7 @@ class ModalNavbar extends StatelessWidget { ModalProvider.of(context).instance.closeModal(); }, ), + rightAction ?? SizedBox.shrink(), ], ); }, diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart index bb52d63b..a21f5c4e 100644 --- a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_account_button.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:reown_appkit/modal/pages/approve_magic_request_page.dart'; import 'package:reown_appkit/modal/pages/confirm_email_page.dart'; +import 'package:reown_appkit/modal/pages/social_login_page.dart'; import 'package:reown_appkit/modal/services/explorer_service/explorer_service_singleton.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; import 'package:reown_appkit/modal/constants/style_constants.dart'; -import 'package:reown_appkit/modal/utils/render_utils.dart'; -import 'package:reown_appkit/modal/widgets/avatars/account_avatar.dart'; -import 'package:reown_appkit/modal/widgets/buttons/balance_button.dart'; import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; import 'package:reown_appkit/modal/widgets/circular_loader.dart'; @@ -37,10 +36,8 @@ class AppKitModalAccountButton extends StatefulWidget { } class _AppKitModalAccountButtonState extends State { - String _balance = BalanceButton.balanceDefault; + IMagicService get _magicService => GetIt.I(); String _address = ''; - String? _tokenImage; - String? _tokenName; @override void initState() { @@ -48,27 +45,20 @@ class _AppKitModalAccountButtonState extends State { _modalNotifyListener(); widget.appKit.addListener(_modalNotifyListener); // TODO [AppKitModalAccountButton] this should go in ReownAppKitModal but for that, init() method of ReownAppKitModal should receive a BuildContext, which would be a breaking change - magicService.instance.onMagicRpcRequest.subscribe(_approveSign); - magicService.instance.onMagicLoginRequest.subscribe(_loginRequested); + _magicService.onMagicRpcRequest.subscribe(_approveSign); + _magicService.onMagicLoginRequest.subscribe(_loginRequested); } @override void dispose() { widget.appKit.removeListener(_modalNotifyListener); - magicService.instance.onMagicRpcRequest.unsubscribe(_approveSign); - magicService.instance.onMagicLoginRequest.unsubscribe(_loginRequested); + _magicService.onMagicRpcRequest.unsubscribe(_approveSign); + _magicService.onMagicLoginRequest.unsubscribe(_loginRequested); super.dispose(); } void _modalNotifyListener() { - setState(() { - _address = widget.appKit.session?.address ?? ''; - final chainId = widget.appKit.selectedChain?.chainId ?? ''; - final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); - _tokenImage = explorerService.instance.getAssetImageUrl(imageId); - _balance = widget.appKit.chainBalance; - _tokenName = widget.appKit.selectedChain?.currency; - }); + setState(() => _address = widget.appKit.session?.address ?? ''); } void _onTap() { @@ -86,10 +76,25 @@ class _AppKitModalAccountButtonState extends State { } void _loginRequested(MagicSessionEvent? args) { - if (widget.appKit.isOpen) { - widgetStack.instance.popAllAndPush(ConfirmEmailPage()); + if (args == null) return; + final provider = args.provider; + final isOpen = widget.appKit.isOpen; + if (isOpen) { + if (provider != null) { + widgetStack.instance.popAllAndPush(SocialLoginPage( + socialOption: provider, + )); + } else { + widgetStack.instance.popAllAndPush(ConfirmEmailPage()); + } } else { - widget.appKit.openModalView(ConfirmEmailPage()); + if (provider != null) { + widget.appKit.openModalView(SocialLoginPage( + socialOption: provider, + )); + } else { + widget.appKit.openModalView(ConfirmEmailPage()); + } } } @@ -145,20 +150,18 @@ class _AppKitModalAccountButtonState extends State { mainAxisSize: MainAxisSize.min, children: [ _BalanceButton( - isLoading: widget.appKit.status.isLoading, - balance: _balance, - tokenName: _tokenName, - tokenImage: _tokenImage, - iconSize: widget.size.iconSize, + appKit: widget.appKit, buttonSize: widget.size, onTap: enabled ? _onTap : null, ), const SizedBox.square(dimension: 4.0), - _AddressButton( - address: _address, - buttonSize: widget.size, - appKit: widget.appKit, - onTap: enabled ? _onTap : null, + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: AppKitModalAddressButton( + size: widget.size, + appKitModal: widget.appKit, + onTap: enabled ? _onTap : null, + ), ), ], ), @@ -168,128 +171,14 @@ class _AppKitModalAccountButtonState extends State { } } -class _AddressButton extends StatelessWidget { - const _AddressButton({ - required this.buttonSize, - required this.address, - required this.appKit, - required this.onTap, - }); - final BaseButtonSize buttonSize; - final VoidCallback? onTap; - final String address; - final IReownAppKitModal appKit; - - @override - Widget build(BuildContext context) { - if (address.isEmpty) { - return SizedBox.shrink(); - } - final themeData = ReownAppKitModalTheme.getDataOf(context); - final textStyle = buttonSize == BaseButtonSize.small - ? themeData.textStyles.small600 - : themeData.textStyles.paragraph600; - final themeColors = ReownAppKitModalTheme.colorsOf(context); - final radiuses = ReownAppKitModalTheme.radiusesOf(context); - final innerBorderRadius = - radiuses.isSquare() ? 0.0 : BaseButtonSize.small.height / 2; - return Padding( - padding: EdgeInsets.only( - top: buttonSize == BaseButtonSize.small ? 4.0 : 0.0, - bottom: buttonSize == BaseButtonSize.small ? 4.0 : 0.0, - ), - child: BaseButton( - size: BaseButtonSize.small, - onTap: onTap, - overridePadding: MaterialStateProperty.all( - EdgeInsets.only( - left: buttonSize == BaseButtonSize.small ? 4.0 : 6.0, - right: 8.0, - ), - ), - buttonStyle: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (states) { - if (states.contains(MaterialState.disabled)) { - return themeColors.grayGlass005; - } - return themeColors.grayGlass010; - }, - ), - foregroundColor: MaterialStateProperty.resolveWith( - (states) { - if (states.contains(MaterialState.disabled)) { - return themeColors.grayGlass015; - } - return themeColors.foreground175; - }, - ), - shape: MaterialStateProperty.resolveWith( - (states) { - return RoundedRectangleBorder( - side: states.contains(MaterialState.disabled) - ? BorderSide( - color: themeColors.grayGlass005, - width: 1.0, - ) - : BorderSide( - color: themeColors.grayGlass010, - width: 1.0, - ), - borderRadius: BorderRadius.circular(innerBorderRadius), - ); - }, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(buttonSize.iconSize), - border: Border.all( - color: themeColors.grayGlass005, - width: 1.0, - strokeAlign: BorderSide.strokeAlignInside, - ), - ), - child: AccountAvatar( - appKit: appKit, - size: buttonSize.iconSize, - disabled: false, - ), - ), - const SizedBox.square(dimension: 4.0), - Text( - RenderUtils.truncate( - address, - length: buttonSize == BaseButtonSize.small ? 2 : 4, - ), - style: textStyle, - ), - ], - ), - ), - ); - } -} - class _BalanceButton extends StatelessWidget { const _BalanceButton({ + required this.appKit, required this.onTap, - required this.isLoading, - required this.balance, - required this.tokenName, - required this.tokenImage, - required this.iconSize, required this.buttonSize, }); + final IReownAppKitModal appKit; final VoidCallback? onTap; - final bool isLoading; - final String balance; - final String? tokenName; - final String? tokenImage; - final double iconSize; final BaseButtonSize buttonSize; @override @@ -299,6 +188,9 @@ class _BalanceButton extends StatelessWidget { final textStyle = buttonSize == BaseButtonSize.small ? themeData.textStyles.small600 : themeData.textStyles.paragraph600; + final chainId = appKit.selectedChain?.chainId ?? ''; + final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); + final tokenImage = explorerService.instance.getAssetImageUrl(imageId); return BaseButton( size: BaseButtonSize.small, onTap: onTap, @@ -319,7 +211,7 @@ class _BalanceButton extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - isLoading + appKit.status.isLoading ? Row( children: [ const SizedBox.square(dimension: kPadding6), @@ -330,21 +222,26 @@ class _BalanceButton extends StatelessWidget { const SizedBox.square(dimension: kPadding6), ], ) - : (tokenImage ?? '').isEmpty + : tokenImage.isEmpty ? RoundedIcon( assetPath: 'lib/modal/assets/icons/network.svg', - size: iconSize, + size: buttonSize.iconSize, assetColor: themeColors.inverse100, padding: 4.0, ) : RoundedIcon( imageUrl: tokenImage, - size: iconSize + 2.0, + size: buttonSize.iconSize + 2.0, ), const SizedBox.square(dimension: 4.0), - Text( - '$balance ${tokenName ?? ''}', - style: textStyle, + ValueListenableBuilder( + valueListenable: appKit.balanceNotifier, + builder: (_, balance, __) { + return Text( + balance, + style: textStyle, + ); + }, ), ], ), diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_address_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_address_button.dart new file mode 100644 index 00000000..8a0d3954 --- /dev/null +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_address_button.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; +import 'package:reown_appkit/modal/utils/render_utils.dart'; +import 'package:reown_appkit/modal/widgets/avatars/account_avatar.dart'; +import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; +import 'package:reown_appkit/reown_appkit.dart'; + +class AppKitModalAddressButton extends StatelessWidget { + const AppKitModalAddressButton({ + super.key, + required this.appKitModal, + this.size = BaseButtonSize.regular, + this.onTap, + }); + final IReownAppKitModal appKitModal; + final BaseButtonSize size; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + if ((appKitModal.session?.address ?? '').isEmpty) { + return SizedBox.shrink(); + } + final address = appKitModal.session!.address!; + final themeData = ReownAppKitModalTheme.getDataOf(context); + final textStyle = size == BaseButtonSize.small + ? themeData.textStyles.small600 + : themeData.textStyles.paragraph600; + final themeColors = ReownAppKitModalTheme.colorsOf(context); + final radiuses = ReownAppKitModalTheme.radiusesOf(context); + final innerBorderRadius = radiuses.isSquare() ? 0.0 : size.height / 2; + return Padding( + padding: EdgeInsets.only( + top: size == BaseButtonSize.small ? 4.0 : 0.0, + bottom: size == BaseButtonSize.small ? 4.0 : 0.0, + ), + child: BaseButton( + size: size, + onTap: onTap, + overridePadding: MaterialStateProperty.all( + EdgeInsets.only( + left: size == BaseButtonSize.small ? 4.0 : 6.0, + right: 8.0, + ), + ), + buttonStyle: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass005; + } + return themeColors.grayGlass010; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return themeColors.grayGlass015; + } + return themeColors.foreground175; + }, + ), + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + side: states.contains(MaterialState.disabled) + ? BorderSide( + color: themeColors.grayGlass005, + width: 1.0, + ) + : BorderSide( + color: themeColors.grayGlass010, + width: 1.0, + ), + borderRadius: BorderRadius.circular(innerBorderRadius), + ); + }, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size.iconSize), + border: Border.all( + color: themeColors.grayGlass005, + width: 1.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + ), + child: AccountAvatar( + appKit: appKitModal, + size: size.iconSize * 0.95, + disabled: false, + ), + ), + const SizedBox.square(dimension: 4.0), + Text( + RenderUtils.truncate( + address, + length: size == BaseButtonSize.small ? 2 : 4, + ), + style: textStyle, + ), + ], + ), + ), + ); + } +} diff --git a/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_balance_button.dart similarity index 76% rename from packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart rename to packages/reown_appkit/lib/modal/widgets/public/appkit_modal_balance_button.dart index 2c100d70..6cb3f2e1 100644 --- a/packages/reown_appkit/lib/modal/widgets/buttons/balance_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_balance_button.dart @@ -6,50 +6,44 @@ import 'package:reown_appkit/modal/utils/public/appkit_modal_default_networks.da import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; import 'package:reown_appkit/modal/widgets/icons/rounded_icon.dart'; -// Export -class BalanceButton extends StatefulWidget { - static const balanceDefault = '_._'; - - const BalanceButton({ +class AppKitModalBalanceButton extends StatefulWidget { + const AppKitModalBalanceButton({ super.key, - required this.service, + required this.appKitModal, this.size = BaseButtonSize.regular, this.onTap, }); - - final IReownAppKitModal service; + static const balanceDefault = '_.__'; + final IReownAppKitModal appKitModal; final BaseButtonSize size; final VoidCallback? onTap; @override - State createState() => _BalanceButtonState(); + State createState() => + _AppKitModalBalanceButtonState(); } -class _BalanceButtonState extends State { - String _balance = BalanceButton.balanceDefault; +class _AppKitModalBalanceButtonState extends State { String? _tokenImage; - String? _tokenName; @override void initState() { super.initState(); _modalNotifyListener(); - widget.service.addListener(_modalNotifyListener); + widget.appKitModal.addListener(_modalNotifyListener); } @override void dispose() { - widget.service.removeListener(_modalNotifyListener); + widget.appKitModal.removeListener(_modalNotifyListener); super.dispose(); } void _modalNotifyListener() { setState(() { - final chainId = widget.service.selectedChain?.chainId ?? '1'; + final chainId = widget.appKitModal.selectedChain?.chainId ?? '1'; final imageId = ReownAppKitModalNetworks.getNetworkIconId(chainId); _tokenImage = explorerService.instance.getAssetImageUrl(imageId); - _balance = widget.service.chainBalance; - _tokenName = widget.service.selectedChain?.currency; }); } @@ -98,10 +92,15 @@ class _BalanceButtonState extends State { children: [ RoundedIcon( imageUrl: _tokenImage, - size: widget.size.height * 0.7, + size: widget.size.height * 0.55, ), const SizedBox.square(dimension: 4.0), - Text('$_balance ${_tokenName ?? ''}'), + ValueListenableBuilder( + valueListenable: widget.appKitModal.balanceNotifier, + builder: (_, balance, __) { + return Text(balance); + }, + ), ], ), ); diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart index 07078042..38871099 100644 --- a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_connect_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:reown_appkit/modal/services/magic_service/magic_service_singleton.dart'; +import 'package:get_it/get_it.dart'; +import 'package:reown_appkit/modal/services/magic_service/i_magic_service.dart'; import 'package:reown_appkit/modal/services/magic_service/models/magic_events.dart'; import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; @@ -110,17 +111,18 @@ class _WebViewWidget extends StatefulWidget { } class _WebViewWidgetState extends State<_WebViewWidget> { + IMagicService get _magicService => GetIt.I(); bool _show = true; // @override void initState() { super.initState(); - magicService.instance.onMagicRpcRequest.subscribe(_onRequest); + _magicService.onMagicRpcRequest.subscribe(_onRequest); } @override void dispose() { - magicService.instance.onMagicRpcRequest.unsubscribe(_onRequest); + _magicService.onMagicRpcRequest.unsubscribe(_onRequest); super.dispose(); } @@ -134,12 +136,13 @@ class _WebViewWidgetState extends State<_WebViewWidget> { @override Widget build(BuildContext context) { - final emailEnabled = magicService.instance.isEnabled.value; - if (emailEnabled && _show) { + final emailEnabled = _magicService.isEmailEnabled.value; + final socialEnabled = _magicService.isSocialEnabled.value; + if ((emailEnabled || socialEnabled) && _show) { return SizedBox( width: 0.5, height: 0.5, - child: magicService.instance.webview, + child: _magicService.webview, ); } return const SizedBox.shrink(); diff --git a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart index 317f50d5..71e0c332 100644 --- a/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart +++ b/packages/reown_appkit/lib/modal/widgets/public/appkit_modal_widgets.dart @@ -1,6 +1,8 @@ +export 'appkit_modal_account_button.dart'; +export 'appkit_modal_address_button.dart'; +export 'appkit_modal_balance_button.dart'; export 'appkit_modal_connect_button.dart'; export 'appkit_modal_network_select_button.dart'; -export 'appkit_modal_account_button.dart'; export '../buttons/base_button.dart' show BaseButtonSize; export '../buttons/connect_button.dart' show ConnectButtonState; diff --git a/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart b/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart index 3671578e..d449b1c7 100644 --- a/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart +++ b/packages/reown_appkit/lib/modal/widgets/text/appkit_balance.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; import 'package:reown_appkit/modal/theme/public/appkit_modal_theme.dart'; -import 'package:reown_appkit/modal/widgets/buttons/balance_button.dart'; import 'package:reown_appkit/modal/widgets/modal_provider.dart'; import 'package:reown_appkit/modal/widgets/buttons/base_button.dart'; @@ -20,43 +19,46 @@ class BalanceText extends StatefulWidget { } class _BalanceTextState extends State { - String _balance = BalanceButton.balanceDefault; - String? _tokenName; - IReownAppKitModal? _service; + IReownAppKitModal? _appKitModal; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _service = ModalProvider.of(context).instance; + _appKitModal = ModalProvider.of(context).instance; _modalNotifyListener(); - _service?.addListener(_modalNotifyListener); + _appKitModal?.addListener(_modalNotifyListener); }); } @override void dispose() { - _service?.removeListener(_modalNotifyListener); + _appKitModal?.removeListener(_modalNotifyListener); super.dispose(); } void _modalNotifyListener() { - if (_service == null) return; - setState(() { - _balance = _service!.chainBalance; - _tokenName = _service?.selectedChain?.currency; - }); + if (_appKitModal == null) return; + setState(() {}); } @override Widget build(BuildContext context) { final themeData = ReownAppKitModalTheme.getDataOf(context); final themeColors = ReownAppKitModalTheme.colorsOf(context); - return Text( - '$_balance ${_tokenName ?? ''}', - style: themeData.textStyles.paragraph500.copyWith( - color: themeColors.foreground200, - ), + if (_appKitModal == null) { + return const SizedBox.shrink(); + } + return ValueListenableBuilder( + valueListenable: _appKitModal!.balanceNotifier, + builder: (_, balance, __) { + return Text( + balance, + style: themeData.textStyles.paragraph500.copyWith( + color: themeColors.foreground200, + ), + ); + }, ); } } diff --git a/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart b/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart index 3a59bd7b..a18de2fd 100644 --- a/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart +++ b/packages/reown_appkit/lib/modal/widgets/value_listenable_builders/network_service_items_listener.dart @@ -29,14 +29,14 @@ class NetworkServiceItemsListener extends StatelessWidget { valueListenable: networkService.instance.itemList, builder: (context, items, _) { final service = ModalProvider.of(context).instance; - final supportedChains = service.getAvailableChains(); - final parsedItems = supportedChains == null + final availableChains = service.getAvailableChains(); + final parsedItems = availableChains == null ? items : items.map((e) { final caip2Chain = '${CoreConstants.namespace}:${e.data.chainId}'; return e.copyWith( - disabled: !supportedChains.contains(caip2Chain), + disabled: !availableChains.contains(caip2Chain), ); }).toList() ..sort((a, b) { diff --git a/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart b/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart index 55bdf396..6d866844 100644 --- a/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/i_widget_stack.dart @@ -11,6 +11,7 @@ abstract class IWidgetStack with ChangeNotifier { void push( Widget widget, { bool renderScreen = false, + bool replace = false, AnalyticsEvent? event, }); diff --git a/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart index 34b70890..eae8f53b 100644 --- a/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart +++ b/packages/reown_appkit/lib/modal/widgets/widget_stack/widget_stack.dart @@ -18,6 +18,7 @@ class WidgetStack extends IWidgetStack { @override void push( Widget widget, { + bool replace = false, bool renderScreen = false, AnalyticsEvent? event, }) { @@ -25,6 +26,9 @@ class WidgetStack extends IWidgetStack { analyticsService.instance.sendEvent(event); } onRenderScreen.value = renderScreen; + if (replace) { + _stack.removeLast(); + } _stack.add(widget); notifyListeners(); } diff --git a/packages/reown_appkit/lib/version.dart b/packages/reown_appkit/lib/version.dart index 92a0d667..bb83cf94 100644 --- a/packages/reown_appkit/lib/version.dart +++ b/packages/reown_appkit/lib/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '1.0.1'; +const packageVersion = '1.1.0-beta01'; diff --git a/packages/reown_appkit/pubspec.yaml b/packages/reown_appkit/pubspec.yaml index 830494fb..96c98d01 100644 --- a/packages/reown_appkit/pubspec.yaml +++ b/packages/reown_appkit/pubspec.yaml @@ -1,6 +1,6 @@ name: reown_appkit description: "Reown is the onchain UX platform that provides toolkits built on top of the WalletConnect Network" -version: 1.0.1 +version: 1.1.0-beta01 homepage: https://github.com/reown-com/reown_flutter repository: https://github.com/reown-com/reown_flutter/tree/master/packages/reown_appkit documentation: https://docs.reown.com/appkit/flutter/core/installation @@ -10,9 +10,9 @@ environment: dependencies: appcheck: ^1.5.1 - cached_network_image: ^3.3.1 + cached_network_image: ^3.4.0 coinbase_wallet_sdk: ^1.0.10 - collection: ^1.17.2 + collection: ^1.18.0 convert: ^3.1.1 cupertino_icons: ^1.0.2 custom_sliding_segmented_control: ^1.8.1 @@ -21,32 +21,32 @@ dependencies: sdk: flutter flutter_svg: ^2.0.10+1 freezed_annotation: ^2.4.1 + get_it: ^8.0.0 http: ^1.1.2 json_annotation: ^4.8.1 plugin_platform_interface: ^2.1.8 qr_flutter_wc: ^0.0.3 - reown_core: ^1.0.0 - reown_sign: ^1.0.0 + reown_core: ^1.0.1 + reown_sign: ^1.0.1 shimmer: ^3.0.0 - url_launcher: ^6.2.5 - uuid: ^4.3.3 - webview_flutter: ^4.7.0 - webview_flutter_android: ^3.16.0 - webview_flutter_wkwebview: ^3.13.0 + uuid: ^4.5.1 + webview_flutter: ^4.8.0 + webview_flutter_android: ^3.16.3 + webview_flutter_wkwebview: ^3.14.0 dev_dependencies: build_runner: ^2.4.7 build_version: ^2.1.1 dependency_validator: ^3.2.2 - flutter_lints: ^2.0.0 flutter_test: sdk: flutter freezed: ^2.4.5 json_serializable: ^6.7.0 + lints: ^4.0.0 logger: ^2.2.0 mockito: ^5.4.3 - package_info_plus: ^7.0.0 - reown_walletkit: ^1.0.0 + package_info_plus: ^8.0.2 + reown_walletkit: ^1.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj index 611bd659..a3f0416b 100644 --- a/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/reown_walletkit/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,13 +8,13 @@ /* Begin PBXBuildFile section */ 091D8F542C4A7A5000904D6C /* Info-internal.plist in Resources */ = {isa = PBXBuildFile; fileRef = 091D8F532C4A7A5000904D6C /* Info-internal.plist */; }; - 105D8BDF14CD603AEEBDCC8B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D52CA33F2483125AF2DE0416 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 9BE64AD39D412E74E7A41E07 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D653E099EE390AEBCBF8FC57 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -33,15 +33,14 @@ /* Begin PBXFileReference section */ 091D8F532C4A7A5000904D6C /* Info-internal.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-internal.plist"; sourceTree = ""; }; 0978D7E02C6B682E00E3593C /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 09EE3E8E42FD659E0B4D7BB1 /* Pods-Runner.debug-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1C3C261AB4BD043406C866FF /* Pods-Runner.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 4CB23A5CBC8BD6767E2C2F44 /* Pods-Runner.profile-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-internal.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 75482D7D38522B70B9D7121A /* Pods-Runner.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7F13B76DCA4B90C0AE5308D9 /* Pods-Runner.release-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-internal.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -49,10 +48,11 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B261CC0672AF36F508F41C85 /* Pods-Runner.debug-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-internal.xcconfig"; sourceTree = ""; }; - CE0466A213D9EF68517190E8 /* Pods-Runner.profile-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-production.xcconfig"; sourceTree = ""; }; - D1922DD9E72FD472EDDECD85 /* Pods-Runner.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig"; sourceTree = ""; }; - D52CA33F2483125AF2DE0416 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AFAE38CBB7C347E0F6EA228 /* Pods-Runner.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-production.xcconfig"; sourceTree = ""; }; + C5C38FC2FB137A8DB7737C2D /* Pods-Runner.profile-internal.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-internal.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-internal.xcconfig"; sourceTree = ""; }; + CE1FDD150CAC5C2D25BBCBB4 /* Pods-Runner.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-production.xcconfig"; sourceTree = ""; }; + D653E099EE390AEBCBF8FC57 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC2E0D1AB6C7D5CA0B096C7D /* Pods-Runner.profile-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-production.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-production.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,17 +60,17 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 105D8BDF14CD603AEEBDCC8B /* Pods_Runner.framework in Frameworks */, + 9BE64AD39D412E74E7A41E07 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 66733AE7956BEAF5F88C1B69 /* Frameworks */ = { + 79F7800211283132B8A40025 /* Frameworks */ = { isa = PBXGroup; children = ( - D52CA33F2483125AF2DE0416 /* Pods_Runner.framework */, + D653E099EE390AEBCBF8FC57 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -93,7 +93,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, C4281BF2C021B5570082F0DC /* Pods */, - 66733AE7956BEAF5F88C1B69 /* Frameworks */, + 79F7800211283132B8A40025 /* Frameworks */, ); sourceTree = ""; }; @@ -125,12 +125,12 @@ C4281BF2C021B5570082F0DC /* Pods */ = { isa = PBXGroup; children = ( - 75482D7D38522B70B9D7121A /* Pods-Runner.debug-production.xcconfig */, - B261CC0672AF36F508F41C85 /* Pods-Runner.debug-internal.xcconfig */, - D1922DD9E72FD472EDDECD85 /* Pods-Runner.release-production.xcconfig */, - 7F13B76DCA4B90C0AE5308D9 /* Pods-Runner.release-internal.xcconfig */, - CE0466A213D9EF68517190E8 /* Pods-Runner.profile-production.xcconfig */, - 4CB23A5CBC8BD6767E2C2F44 /* Pods-Runner.profile-internal.xcconfig */, + 9AFAE38CBB7C347E0F6EA228 /* Pods-Runner.debug-production.xcconfig */, + 09EE3E8E42FD659E0B4D7BB1 /* Pods-Runner.debug-internal.xcconfig */, + CE1FDD150CAC5C2D25BBCBB4 /* Pods-Runner.release-production.xcconfig */, + 1C3C261AB4BD043406C866FF /* Pods-Runner.release-internal.xcconfig */, + DC2E0D1AB6C7D5CA0B096C7D /* Pods-Runner.profile-production.xcconfig */, + C5C38FC2FB137A8DB7737C2D /* Pods-Runner.profile-internal.xcconfig */, ); path = Pods; sourceTree = ""; @@ -142,14 +142,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 4E461C07796703DAE0B20A8D /* [CP] Check Pods Manifest.lock */, + D1A8417ECB78F1B2893E1798 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - D1ADDB5BA932C42DF23062F2 /* [CP] Embed Pods Frameworks */, + 6493043A7933D573920485DE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -225,26 +225,21 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 4E461C07796703DAE0B20A8D /* [CP] Check Pods Manifest.lock */ = { + 6493043A7933D573920485DE /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -262,21 +257,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - D1ADDB5BA932C42DF23062F2 /* [CP] Embed Pods Frameworks */ = { + D1A8417ECB78F1B2893E1798 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -787,9 +787,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = W5R8AG9K22; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "FL WalletKit"; @@ -800,6 +802,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.flutterwallet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.flutterwallet"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/packages/reown_walletkit/generate_files.sh b/packages/reown_walletkit/generate_files.sh index b99efbc7..deec0b81 100644 --- a/packages/reown_walletkit/generate_files.sh +++ b/packages/reown_walletkit/generate_files.sh @@ -20,6 +20,7 @@ dart run dependency_validator cd ios +# rm Podfile.lock # pod deintegrate # pod cache clean -all pod install