diff --git a/assets/config/v4/communities.json b/assets/config/v4/communities.json index 766c3cfa..7d61b4b6 100644 --- a/assets/config/v4/communities.json +++ b/assets/config/v4/communities.json @@ -590,6 +590,30 @@ "launch_mode": "webview", "signature": true, "hidden": true + }, + { + "name": "Japan Demo", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "url": "https://hackatsuonvoting.vercel.app", + "launch_mode": "webview", + "signature": true, + "hidden": true + }, + { + "name": "Japan Demo", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "url": "https://grow-kesennuma.vercel.app", + "launch_mode": "webview", + "signature": true, + "hidden": true + }, + { + "name": "Mutual Vend", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "url": "https://mutual-vend.com", + "launch_mode": "webview", + "signature": true, + "hidden": true } ], "config_location": "https://config.internal.citizenwallet.xyz/v4/bread.citizenwallet.xyz.json", diff --git a/assets/config/v4/communities.test.json b/assets/config/v4/communities.test.json index d9f6e52b..f61f3b08 100644 --- a/assets/config/v4/communities.test.json +++ b/assets/config/v4/communities.test.json @@ -590,6 +590,30 @@ "launch_mode": "webview", "signature": true, "hidden": true + }, + { + "name": "Japan Demo", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "url": "https://hackatsuonvoting.vercel.app", + "launch_mode": "webview", + "signature": true, + "hidden": true + }, + { + "name": "Japan Demo", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "url": "https://grow-kesennuma.vercel.app", + "launch_mode": "webview", + "signature": true, + "hidden": true + }, + { + "name": "Mutual Vend", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/bread.svg", + "url": "https://mutual-vend.com", + "launch_mode": "webview", + "signature": true, + "hidden": true } ], "config_location": "https://config.internal.citizenwallet.xyz/v4/bread.citizenwallet.xyz.json", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index cf296501..c30fca89 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -159,9 +159,9 @@ PODS: - reown_yttrium (0.0.1): - Flutter - YttriumWrapper (= 0.8.35) - - SDWebImage (5.21.0): - - SDWebImage/Core (= 5.21.0) - - SDWebImage/Core (5.21.0) + - SDWebImage (5.21.2): + - SDWebImage/Core (= 5.21.2) + - SDWebImage/Core (5.21.2) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -265,43 +265,43 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f - audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 - connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d - credential_manager: feb21034894e469e3686461dc96fb24bb7d350e4 + audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + credential_manager: e32c96e222e95368067810f7529eea06d9c5c171 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: be9a674155d9f334323856cb266e0d145d75d5c0 + file_picker: 6d4bf27b7318804adf4ddc1827dbc5cd4b78042d Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_core: 3c2f323cae65c97a636a05a23b17730ef93df2cf - firebase_messaging: 456e01ff29a451c90097d0b45925551d5be0c143 + firebase_core: ba71b44041571da878cb624ce0d80250bcbe58ad + firebase_messaging: 13129fe2ca166d1ed2d095062d76cee88943d067 FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - google_sign_in_ios: 7411fab6948df90490dc4620ecbcabdc3ca04017 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a - mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e + icloud_storage: e55639f0c0d7cb2b0ba9c0b3d5968ccca9cd9aa2 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - nfc_manager: d7da7cb781f7744b94df5fe9dbca904ac4a0939e + nfc_manager: 9c40fe22528ab871ca11e52ea8b95790e9d92ca2 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 - SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58 + SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 PODFILE CHECKSUM: f90b7b7d52ec0d905039aa6f51266424548151c7 diff --git a/lib/router/router.dart b/lib/router/router.dart index a2883c19..a5a5daca 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -56,8 +56,12 @@ GoRouter createRouter( builder: (context, state) { final appState = Provider.of(context, listen: true); - // coming in from a deep link "#/wallet/..." will come as a fragment - final uri = Uri.parse(state.uri.fragment); + Uri uri = Uri.parse(state.uri.fragment); + + if (uri.queryParameters.isEmpty && + state.uri.queryParameters.isNotEmpty) { + uri = state.uri; + } // parse from a deep link String? deepLinkParams; @@ -72,9 +76,20 @@ GoRouter createRouter( String? sendToParams; final sendTo = uri.queryParameters['sendto']; final eip681 = uri.queryParameters['eip681']; + final tipTo = uri.queryParameters['tipTo']; + if (sendTo != null) { - sendToParams = - encodeParams(uri.toString().replaceFirst('/?', '')); + sendToParams = 'sendto=$sendTo'; + if (tipTo != null) { + sendToParams += '&tipTo=$tipTo'; + } + if (uri.queryParameters['amount'] != null) { + sendToParams += '&amount=${uri.queryParameters['amount']}'; + } + if (uri.queryParameters['description'] != null) { + sendToParams += + '&description=${uri.queryParameters['description']}'; + } } else if (eip681 != null) { sendToParams = encodeParams(uri.toString().replaceFirst('/?', '')); @@ -157,6 +172,30 @@ GoRouter createRouter( deepLinkParams = state.uri.queryParameters[deepLink]; } + String? sendToURL; + final sendToParams = state.uri.queryParameters['sendToParams']; + if (sendToParams != null) { + sendToURL = 'https://app.citizenwallet.xyz/?$sendToParams'; + } else { + final sendTo = state.uri.queryParameters['sendto']; + final tipTo = state.uri.queryParameters['tipTo']; + final amount = state.uri.queryParameters['amount']; + final description = state.uri.queryParameters['description']; + if (sendTo != null) { + String params = 'sendto=$sendTo'; + if (tipTo != null) { + params += '&tipTo=$tipTo'; + } + if (amount != null) { + params += '&amount=$amount'; + } + if (description != null) { + params += '&description=$description'; + } + sendToURL = 'https://app.citizenwallet.xyz/?$params'; + } + } + return NoTransitionPage( key: state.pageKey, name: state.name, @@ -169,6 +208,7 @@ GoRouter createRouter( receiveParams: state.uri.queryParameters['receiveParams'], deepLink: deepLink, deepLinkParams: deepLinkParams, + sendToURL: sendToURL, ), ); }, @@ -504,6 +544,7 @@ GoRouter createWebRouter( } } + return WebLandingScreen( voucher: state.uri.queryParameters['voucher'], voucherParams: state.uri.queryParameters['params'], diff --git a/lib/router/utils.dart b/lib/router/utils.dart index a1c1e65f..93419ba6 100644 --- a/lib/router/utils.dart +++ b/lib/router/utils.dart @@ -2,15 +2,19 @@ import 'package:citizenwallet/services/wallet/utils.dart'; import 'package:citizenwallet/utils/qr.dart'; (String?, String?, String?) deepLinkParamsFromUri(String uri) { - final fragment = Uri.parse(uri).fragment; - - // Handle the case where fragment starts with /? + final parsedUri = Uri.parse(uri); + + final fragment = parsedUri.fragment; String queryString = fragment; if (fragment.startsWith('/?')) { queryString = fragment.substring(2); } - - final uriData = Uri.parse('temp://temp?$queryString'); + + Uri uriData = Uri.parse('temp://temp?$queryString'); + + if (fragment.isEmpty || uriData.queryParameters.isEmpty) { + uriData = parsedUri; + } String? voucherParams = uriData.queryParameters['params']; // String? receiveParams = uriData.queryParameters['receiveParams']; @@ -19,6 +23,10 @@ import 'package:citizenwallet/utils/qr.dart'; if (uriData.queryParameters['sendto'] != null) { sendToParams = 'sendto=${uriData.queryParameters['sendto']}${uriData.queryParameters['amount'] != null ? '&amount=${uriData.queryParameters['amount']}' : ''}${uriData.queryParameters['description'] != null ? '&description=${uriData.queryParameters['description']}' : ''}'; + + if (uriData.queryParameters['tipTo'] != null) { + sendToParams += '&tipTo=${uriData.queryParameters['tipTo']}'; + } } else if (uriData.queryParameters['eip681'] != null) { sendToParams = 'eip681=${uriData.queryParameters['eip681']}${uriData.queryParameters['alias'] != null ? '&alias=${uriData.queryParameters['alias']}' : ''}'; @@ -37,42 +45,50 @@ import 'package:citizenwallet/utils/qr.dart'; } (String?, String?) deepLinkContentFromUri(String uri) { - final fragment = Uri.parse(uri).fragment; - - // Handle the case where fragment starts with /? + final parsedUri = Uri.parse(uri); + + final fragment = parsedUri.fragment; String queryString = fragment; if (fragment.startsWith('/?')) { queryString = fragment.substring(2); } - - final uriData = Uri.parse('temp://temp?$queryString'); + + Uri uriData = Uri.parse('temp://temp?$queryString'); + + if (fragment.isEmpty || uriData.queryParameters.isEmpty) { + uriData = parsedUri; + } return (uriData.queryParameters['voucher'], uriData.queryParameters['dl']); } String? aliasFromUri(String uri) { - final fragment = Uri.parse(uri).fragment; - - // Handle the case where fragment starts with /? + final parsedUri = Uri.parse(uri); + + final fragment = parsedUri.fragment; String queryString = fragment; if (fragment.startsWith('/?')) { queryString = fragment.substring(2); } - - final uriData = Uri.parse('temp://temp?$queryString'); + + Uri uriData = Uri.parse('temp://temp?$queryString'); + + if (fragment.isEmpty || uriData.queryParameters.isEmpty) { + uriData = parsedUri; + } return uriData.queryParameters['alias']; } String? aliasFromDeepLinkUri(String uri) { final fragment = Uri.parse(uri).fragment; - + // Handle the case where fragment starts with /? String queryString = fragment; if (fragment.startsWith('/?')) { queryString = fragment.substring(2); } - + final uriData = Uri.parse('temp://temp?$queryString'); final deepLinkName = uriData.queryParameters['dl']; @@ -98,13 +114,13 @@ String? aliasFromReceiveUri(String uri) { } final fragment = Uri.parse(uri).fragment; - + // Handle the case where fragment starts with /? String queryString = fragment; if (fragment.startsWith('/?')) { queryString = fragment.substring(2); } - + final uriData = Uri.parse('temp://temp?$queryString'); final compressedParams = uriData.queryParameters['receiveParams']; @@ -121,27 +137,26 @@ String? aliasFromReceiveUri(String uri) { String? aliasFromSendUri(String uri) { final format = parseQRFormat(uri); - + if (format != QRFormat.sendtoUrl && format != QRFormat.sendtoUrlWithEIP681) { + try { + final parsedUri = Uri.parse(uri); + final sendToParam = parsedUri.queryParameters['sendto']; + if (sendToParam != null && sendToParam.contains('@')) { + final alias = sendToParam.split('@').last; + return alias; + } + } catch (e) { + // + } return null; } - final fragment = Uri.parse(uri).fragment; - - // Handle the case where fragment starts with /? - String queryString = fragment; - if (fragment.startsWith('/?')) { - queryString = fragment.substring(2); - } - - final uriData = Uri.parse('temp://temp?$queryString'); - switch (format) { case QRFormat.sendtoUrl: - final parsedData = parseSendtoUrl(uriData.toString()); + final parsedData = parseSendtoUrl(uri); return parsedData.alias; case QRFormat.sendtoUrlWithEIP681: - // For sendtoUrlWithEIP681, we need to parse the original URI, not the dummy one final parsedData = parseSendtoUrlWithEIP681(uri); return parsedData.alias; default: diff --git a/lib/screens/landing/screen.dart b/lib/screens/landing/screen.dart index b8c14204..53836002 100644 --- a/lib/screens/landing/screen.dart +++ b/lib/screens/landing/screen.dart @@ -110,10 +110,6 @@ class LandingScreenState extends State params += '&$deepLink=$deepLinkParams'; } - // if (sendToParams != null) { - // params += '&${Uri.decodeComponent(sendToParams)}'; - // } - if (extra.isNotEmpty) { params += '&${extra.join('&')}'; } @@ -147,6 +143,10 @@ class LandingScreenState extends State alias ??= aliasFromReceiveUri(widget.uri); alias ??= aliasFromSendUri(widget.uri); + if (alias == null && widget.sendToParams != null) { + alias = extractAliasFromSendToParams(widget.sendToParams!); + } + // handle voucher redemption // pick an appropriate wallet to load if (widget.voucher != null && @@ -270,6 +270,26 @@ class LandingScreenState extends State return uri.queryParameters['alias']; } + String? extractAliasFromSendToParams(String sendToParams) { + try { + final uri = Uri(query: sendToParams); + final sendToParam = uri.queryParameters['sendto']; + + if (sendToParam != null && sendToParam.contains('@')) { + final parts = sendToParam.split('@'); + if (parts.length >= 2) { + final alias = parts.last; + return alias; + } + } + + return null; + } catch (e) { + // + return null; + } + } + /// handleAppleRecover handles the apple recover flow if needed and then returns Future handleAppleRecover() async { if (!isPlatformApple()) { diff --git a/lib/screens/send/send_details.dart b/lib/screens/send/send_details.dart index 6b3f8f9e..ca9d9a2e 100644 --- a/lib/screens/send/send_details.dart +++ b/lib/screens/send/send_details.dart @@ -204,7 +204,6 @@ class _SendDetailsScreenState extends State { } final walletLogic = widget.walletLogic; - final walletState = context.read(); FocusManager.instance.primaryFocus?.unfocus(); diff --git a/lib/screens/send/tip_details.dart b/lib/screens/send/tip_details.dart index b6d380c8..7ed89116 100644 --- a/lib/screens/send/tip_details.dart +++ b/lib/screens/send/tip_details.dart @@ -93,8 +93,8 @@ class _TipDetailsScreenState extends State { super.didChangeDependencies(); final tipTo = context.read().tipTo; if (tipTo != null) { - context.read().setHasTip(true); - context.read().setHasAddress(true); + widget.walletLogic.setHasTip(true); + widget.walletLogic.setHasAddress(true); } } @@ -273,9 +273,9 @@ class _TipDetailsScreenState extends State { print('stack: $stackTrace'); } - context.read().setHasTip(false); - context.read().setHasAddress(false); - context.read().setTipTo(null); + widget.walletLogic.setHasTip(false); + widget.walletLogic.setHasAddress(false); + widget.walletLogic.setTipTo(null); await Future.delayed(const Duration(milliseconds: 50)); diff --git a/lib/screens/wallet/receive.dart b/lib/screens/wallet/receive.dart index a9d7c087..30afe332 100644 --- a/lib/screens/wallet/receive.dart +++ b/lib/screens/wallet/receive.dart @@ -117,7 +117,7 @@ class ReceiveScreenState extends State { _selectedProfile = context.read().selectedProfile; }); - context.read().setTipTo(result); + widget.logic.setTipTo(result); // Update QR code with new tip information widget.logic.updateReceiveQR(); } @@ -130,7 +130,7 @@ class ReceiveScreenState extends State { }); widget.profilesLogic.deSelectProfile(); widget.logic.clearAddressController(); - context.read().setTipTo(null); + widget.logic.setTipTo(null); widget.logic.updateReceiveQR(); } diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index 7e695fcc..73aaf54f 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -172,6 +172,19 @@ class WalletScreenState extends State ); } + void _cleanupDeeplinkRoute() { + Future.delayed(const Duration(milliseconds: 100), () { + if (mounted) { + final navigator = GoRouter.of(context); + final cleanRoute = '/wallet/$_address?alias=$_alias'; + + _sendToURL = null; + + navigator.go(cleanRoute); + } + }); + } + void onLoad() async { if (_address == null || _alias == null) { return; @@ -220,6 +233,10 @@ class WalletScreenState extends State if (_sendToURL != null) { await handleSendScreen(sendToURL: _sendToURL); + + _cleanupDeeplinkRoute(); + } else { + // } if (_deepLink != null && _deepLinkParams != null) { @@ -560,13 +577,18 @@ class WalletScreenState extends State final navigator = GoRouter.of(context); - await navigator.push('/wallet/$_address/send', extra: { + final result = await navigator.push('/wallet/$_address/send', extra: { 'walletLogic': _logic, 'profilesLogic': _profilesLogic, 'voucherLogic': _voucherLogic, 'sendToURL': sendToURL, }); + if (result != true && sendToURL != null) { + _logic.clearTipTo(); + _sendToURL = null; + } + _profileLogic.resume(); _profilesLogic.resume(); _voucherLogic.resume(); @@ -775,6 +797,13 @@ class WalletScreenState extends State _profileLogic.clearProfileLink(); _profileLogic.resetAll(); + _sendToURL = null; + _voucher = null; + _voucherParams = null; + _receiveParams = null; + _deepLink = null; + _deepLinkParams = null; + _address = address; _alias = alias; @@ -808,7 +837,7 @@ class WalletScreenState extends State if (alias == null && params != null) { alias = paramsAlias(params); } - + if (alias == null) { return (null, null); } @@ -1048,13 +1077,14 @@ class WalletScreenState extends State final uriAlias = aliasFromUri(result); final receiveAlias = aliasFromReceiveUri(result); final sendAlias = aliasFromSendUri(result); - + if (voucherParams != null || deepLinkParams != null || sendToParams != null) { final (address, alias) = await handleLoadFromParams( voucherParams ?? sendToParams ?? deepLinkParams ?? parsedQRData.alias, - overrideAlias: uriAlias ?? receiveAlias ?? sendAlias ?? parsedQRData.alias, + overrideAlias: + uriAlias ?? receiveAlias ?? sendAlias ?? parsedQRData.alias, ); loadedAddress = address; loadedAlias = alias; @@ -1093,7 +1123,12 @@ class WalletScreenState extends State _receiveParams = null; _deepLink = deepLink; _deepLinkParams = deepLinkParams; - _sendToURL = result; + + if (voucher != null && voucherParams != null) { + _sendToURL = null; + } else { + _sendToURL = result; + } onLoad(); return; diff --git a/lib/state/wallet/logic.dart b/lib/state/wallet/logic.dart index 43eb0c46..5223cac0 100644 --- a/lib/state/wallet/logic.dart +++ b/lib/state/wallet/logic.dart @@ -1665,6 +1665,22 @@ class WalletLogic extends WidgetsBindingObserver { _amountController.clear(); } + void clearTipTo() { + _state.clearTipTo(); + } + + void setTipTo(String? tipTo) { + _state.setTipTo(tipTo); + } + + void setHasTip(bool value) { + _state.setHasTip(value); + } + + void setHasAddress(bool value) { + _state.setHasAddress(value); + } + void resetInputErrorState() { _state.resetInvalidInputs(); } diff --git a/pubspec.lock b/pubspec.lock index bbeda03d..1c61ba3d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -302,10 +302,10 @@ packages: dependency: "direct main" description: name: credential_manager - sha256: "91d54ed2eade14880e381edc604722d400966b6f2e50e87c38eb02ff988c5238" + sha256: "4dbd38e17fb9a8fd30d591826246b7d327d69d7c33bd15e8a9192aa40c178374" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" cross_file: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 845f00e8..acaff689 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: citizenwallet -version: 2.0.25+272 +version: 2.0.28+283 publish_to: none description: A mobile wallet for your community. environment: @@ -61,7 +61,7 @@ dependencies: firebase_messaging: ^15.1.3 firebase_core: ^3.6.0 flutter_inappwebview: ^6.0.0 - credential_manager: ^2.0.1 + credential_manager: ^2.0.4 googleapis: ^14.0.0 path_provider: ^2.1.2 icloud_storage: ^2.2.0