diff --git a/android/app/build.gradle b/android/app/build.gradle index f5a2eb71..a0519186 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -52,7 +52,7 @@ android { applicationId "xyz.citizenwallet.wallet" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion Math.max(flutter.minSdkVersion, 21) + minSdkVersion 23 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/assets/config/v4/communities.json b/assets/config/v4/communities.json index 1e3f8688..9a2ae859 100644 --- a/assets/config/v4/communities.json +++ b/assets/config/v4/communities.json @@ -161,6 +161,19 @@ "action": "topup", "signature": true, "featured": true + }, + { + "name": "Offboard", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/wallet.pay.brussels.png", + "url": "https://my.citizenpay.xyz/cw-migrate", + "launch_mode": "webview", + "signature": true, + "action": "offboard", + "meta": { + "title": "Migrate to Citizen Pay", + "desc": "This community has moved. Migrate your balance over to the Citizen Pay App.", + "button": "Start Migration" + } } ], "config_location": "https://wallet.pay.brussels/config/community.json", diff --git a/assets/config/v4/communities.test.json b/assets/config/v4/communities.test.json index e3d83def..086c4b3d 100644 --- a/assets/config/v4/communities.test.json +++ b/assets/config/v4/communities.test.json @@ -161,6 +161,19 @@ "action": "topup", "signature": true, "featured": true + }, + { + "name": "Offboard", + "icon": "https://assets.citizenwallet.xyz/wallet-config/_images/wallet.pay.brussels.png", + "url": "https://my.citizenpay.xyz/cw-migrate", + "launch_mode": "webview", + "signature": true, + "action": "offboard", + "meta": { + "title": "Migrate to Citizen Pay", + "desc": "This community has moved. Migrate your balance over to the Citizen Pay App.", + "button": "Start Migration" + } } ], "config_location": "https://wallet.pay.brussels/config/community.json", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b35942fc..bbdd1317 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - Flutter - connectivity_plus (0.0.1): - Flutter - - credential_manager (1.0.4): + - credential_manager_ios (2.0.5): - Flutter - DKImagePickerController/Core (4.3.9): - DKImagePickerController/ImageDataManager @@ -49,37 +49,37 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter - - Firebase/CoreOnly (11.10.0): - - FirebaseCore (~> 11.10.0) - - Firebase/Messaging (11.10.0): + - Firebase/CoreOnly (11.15.0): + - FirebaseCore (~> 11.15.0) + - Firebase/Messaging (11.15.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 11.10.0) - - firebase_core (3.13.1): - - Firebase/CoreOnly (= 11.10.0) + - FirebaseMessaging (~> 11.15.0) + - firebase_core (3.15.2): + - Firebase/CoreOnly (= 11.15.0) - Flutter - - firebase_messaging (15.2.6): - - Firebase/Messaging (= 11.10.0) + - firebase_messaging (15.2.10): + - Firebase/Messaging (= 11.15.0) - firebase_core - Flutter - - FirebaseCore (11.10.0): - - FirebaseCoreInternal (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreInternal (11.10.0): - - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseInstallations (11.10.0): - - FirebaseCore (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) + - FirebaseCore (11.15.0): + - FirebaseCoreInternal (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreInternal (11.15.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseInstallations (11.15.0): + - FirebaseCore (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) - PromisesObjC (~> 2.4) - - FirebaseMessaging (11.10.0): - - FirebaseCore (~> 11.10.0) + - FirebaseMessaging (11.15.0): + - FirebaseCore (~> 11.15.0) - FirebaseInstallations (~> 11.0) - GoogleDataTransport (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Reachability (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Reachability (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) - nanopb (~> 3.30910.0) - Flutter (1.0.0) - flutter_inappwebview_ios (0.0.1): @@ -178,7 +178,7 @@ PODS: DEPENDENCIES: - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - - credential_manager (from `.symlinks/plugins/credential_manager/ios`) + - credential_manager_ios (from `.symlinks/plugins/credential_manager_ios/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) @@ -225,8 +225,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/audioplayers_darwin/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" - credential_manager: - :path: ".symlinks/plugins/credential_manager/ios" + credential_manager_ios: + :path: ".symlinks/plugins/credential_manager_ios/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" firebase_core: @@ -267,18 +267,18 @@ SPEC CHECKSUMS: AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d - credential_manager: feb21034894e469e3686461dc96fb24bb7d350e4 + credential_manager_ios: fd1d96ef11fa3d76f868f038342de171c3bc2b28 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: be9a674155d9f334323856cb266e0d145d75d5c0 - Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_core: 3c2f323cae65c97a636a05a23b17730ef93df2cf - firebase_messaging: 456e01ff29a451c90097d0b45925551d5be0c143 - FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 - FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 - FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 - FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 - Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c + firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 + FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e + FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 + FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 + FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 google_sign_in_ios: 7411fab6948df90490dc4620ecbcabdc3ca04017 diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index de8ccc05..44dfcc0e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -199,6 +199,9 @@ "emptyBalanceText2": "Redeem this voucher to your account.", "tapToScan": "Tap to scan", "communityCurrentlyOffline": "Community currently offline", + "communityClosed": "This community is currently closed", + "communityClosedDescription": "This community is not accepting transactions at this time. Contact the community administrator for more information.", + "learnMore": "Learn More", "topup": "Top Up", "close": "Close", "more": "More", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b62ffab5..cf42dcef 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -199,6 +199,9 @@ "emptyBalanceText2": "Canjea este vale en tu cuenta.", "tapToScan": "Toca para escanear", "communityCurrentlyOffline": "Comunidad actualmente fuera de línea", + "communityClosed": "Esta comunidad está actualmente cerrada", + "communityClosedDescription": "Esta comunidad no acepta transacciones en este momento. Ponte en contacto con el administrador de la comunidad para más información.", + "learnMore": "Más información", "topup": "Recargar", "close": "Cerrar", "more": "Más", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 9b4bd69e..4fa97e54 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -198,6 +198,9 @@ "emptyBalanceText2": "Échangez ce bon sur votre compte.", "tapToScan": "Scannez votre carte", "communityCurrentlyOffline": "Communauté actuellement hors ligne", + "communityClosed": "Cette communauté est actuellement fermée", + "communityClosedDescription": "Cette communauté n'accepte pas les transactions pour le moment. Contactez l'administrateur de la communauté pour plus d'informations.", + "learnMore": "En savoir plus", "topup": "Recharger", "more": "Plus", "close": "Fermer", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 42b9531c..f2b1e0a0 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1331,6 +1331,24 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Account not found'** String get accountNotFound; + + /// No description provided for @communityClosed. + /// + /// In en, this message translates to: + /// **'This community is currently closed'** + String get communityClosed; + + /// No description provided for @communityClosedDescription. + /// + /// In en, this message translates to: + /// **'This community is not accepting transactions at this time. Contact the community administrator for more information.'** + String get communityClosedDescription; + + /// No description provided for @learnMore. + /// + /// In en, this message translates to: + /// **'Learn More'** + String get learnMore; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index cf7a7b21..fde6d134 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -667,4 +667,13 @@ class AppLocalizationsEn extends AppLocalizations { @override String get accountNotFound => 'Account not found'; + + @override + String get communityClosed => 'This community is currently closed'; + + @override + String get communityClosedDescription => 'This community is not accepting transactions at this time. Contact the community administrator for more information.'; + + @override + String get learnMore => 'Learn More'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7b0cfb8d..e080fcab 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -671,4 +671,13 @@ class AppLocalizationsEs extends AppLocalizations { @override String get accountNotFound => 'Cuenta no encontrada'; + + @override + String get communityClosed => 'Esta comunidad está actualmente cerrada'; + + @override + String get communityClosedDescription => 'Esta comunidad no está aceptando transacciones en este momento. Contacta al administrador de la comunidad para obtener más información.'; + + @override + String get learnMore => 'Más información'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index f2153012..1f88cc26 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -672,4 +672,13 @@ class AppLocalizationsFr extends AppLocalizations { @override String get accountNotFound => 'Compte non trouvé'; + + @override + String get communityClosed => 'Cette communauté est actuellement fermée'; + + @override + String get communityClosedDescription => 'Cette communauté n\'accepte pas les transactions pour le moment. Contactez l\'administrateur de la communauté pour plus d\'informations.'; + + @override + String get learnMore => 'En savoir plus'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 7eac1ca0..29cff167 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -674,4 +674,13 @@ class AppLocalizationsNl extends AppLocalizations { @override String get accountNotFound => 'Account niet gevonden'; + + @override + String get communityClosed => 'Deze gemeenschap is momenteel gesloten'; + + @override + String get communityClosedDescription => 'Deze gemeenschap accepteert momenteel geen transacties. Neem contact op met de gemeenschapsadministrator voor meer informatie.'; + + @override + String get learnMore => 'Meer informatie'; } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 171bdea4..4d32dbf1 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -198,6 +198,9 @@ "emptyBalanceText2": "Claim deze voucher op je account", "tapToScan": "Tik om te scannen", "communityCurrentlyOffline": "Gemeenschap momenteel offline", + "communityClosed": "Deze gemeenschap is momenteel gesloten", + "communityClosedDescription": "Deze gemeenschap accepteert momenteel geen transacties. Neem contact op met de gemeenschapsbeheerder voor meer informatie.", + "learnMore": "Meer informatie", "topup": "Opwaarderen", "more": "Meer", "close": "Sluiten", diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index 73aaf54f..de525f8e 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -34,6 +34,7 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:provider/provider.dart'; import 'package:citizenwallet/l10n/app_localizations.dart'; import 'package:citizenwallet/widgets/communities/offline_banner.dart'; +import 'package:citizenwallet/widgets/communities/community_closed_banner.dart'; import 'package:reown_walletkit/reown_walletkit.dart'; import 'dart:async'; @@ -85,6 +86,7 @@ class WalletScreenState extends State String? _deepLinkParams; String? _sendToURL; Config? _config; + bool _isClosedBannerDismissed = false; @override void initState() { @@ -1123,7 +1125,7 @@ class WalletScreenState extends State _receiveParams = null; _deepLink = deepLink; _deepLinkParams = deepLinkParams; - + if (voucher != null && voucherParams != null) { _sendToURL = null; } else { @@ -1195,13 +1197,13 @@ class WalletScreenState extends State final eventServiceState = context.select((WalletState state) => state.eventServiceState); - final eventServiceIntentionalDisconnect = context - .select((WalletState state) => state.eventServiceIntentionalDisconnect); - final isOffline = eventServiceState == EventServiceState.error || eventServiceState == EventServiceState.connecting; - final showOfflineBanner = isOffline && !eventServiceIntentionalDisconnect; + final isCommunityClosed = eventServiceState == EventServiceState.closed; + final offboardPlugin = context.select( + (WalletState state) => state.config!.getOffboardPlugin(), + ); final cleaningUp = context.select((WalletState state) => state.cleaningUp); final config = context.select((WalletState state) => state.config); @@ -1262,10 +1264,6 @@ class WalletScreenState extends State ), ), ), - // Positioned( - // bottom: 60, - // left: 0, - // right: 0, Align( alignment: Alignment.bottomCenter, child: Padding( @@ -1275,7 +1273,9 @@ class WalletScreenState extends State crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( - onTap: config?.online == false ? () => () : handleQRScan, + onTap: (config?.online == false || isCommunityClosed) + ? () => () + : handleQRScan, child: Container( height: 90, width: 90, @@ -1286,9 +1286,10 @@ class WalletScreenState extends State .resolveFrom(context), borderRadius: BorderRadius.circular(45), border: Border.all( - color: config?.online == false - ? scanQrDisabledColor - : Theme.of(context).colors.surfacePrimary, + color: + (config?.online == false || isCommunityClosed) + ? scanQrDisabledColor + : Theme.of(context).colors.surfacePrimary, width: 3, ), boxShadow: [ @@ -1309,9 +1310,10 @@ class WalletScreenState extends State child: Icon( CupertinoIcons.qrcode_viewfinder, size: 60, - color: config?.online == false - ? scanQrDisabledColor - : Theme.of(context).colors.surfacePrimary, + color: + (config?.online == false || isCommunityClosed) + ? scanQrDisabledColor + : Theme.of(context).colors.surfacePrimary, ), ), ), @@ -1398,6 +1400,19 @@ class WalletScreenState extends State ), ), ), + if (isCommunityClosed) + CommunityClosedBanner( + handleOffboardPlugin: offboardPlugin != null + ? () => handlePlugin(offboardPlugin) + : null, + onDismiss: () { + setState(() { + _isClosedBannerDismissed = true; + }); + }, + display: isCommunityClosed, + offboardPlugin: offboardPlugin, + ), OfflineBanner( communityUrl: config?.community.url ?? '', display: isOffline, diff --git a/lib/screens/wallet/wallet_actions.dart b/lib/screens/wallet/wallet_actions.dart index 12ae17d3..25bef986 100644 --- a/lib/screens/wallet/wallet_actions.dart +++ b/lib/screens/wallet/wallet_actions.dart @@ -63,24 +63,30 @@ class _WalletActionsState extends State { final actionButton = context.select(selectActionButtonToShow); final plugins = context.select(selectVisiblePlugins); final featuredPlugins = context.select(selectFeaturedPlugins); - final featuredPlugin = featuredPlugins.isNotEmpty ? featuredPlugins.first : null; + final featuredPlugin = + featuredPlugins.isNotEmpty ? featuredPlugins.first : null; final onePlugin = plugins.isNotEmpty ? plugins.first : null; final imageSmall = context.select((ProfileState state) => state.imageSmall); final username = context.select((ProfileState state) => state.username); - final withOfflineBanner = - eventServiceState != EventServiceState.connected && - eventServiceState != EventServiceState.disconnected; + final withOfflineBanner = eventServiceState == EventServiceState.error || + eventServiceState == EventServiceState.connecting; + + final isCommunityClosed = eventServiceState == EventServiceState.closed; final blockSending = context.select(selectShouldBlockSending) || loading || firstLoad || - widget.handleSendScreen == null; + widget.handleSendScreen == null || + isCommunityClosed; final sendLoading = context.read().transactionSendLoading; - final blockReceive = - loading || firstLoad || widget.handleReceive == null || sendLoading; + final blockReceive = loading || + firstLoad || + widget.handleReceive == null || + sendLoading || + isCommunityClosed; final hasPending = context.select(selectHasProcessingTransactions); final newBalance = context.select(selectWalletBalance); @@ -121,22 +127,22 @@ class _WalletActionsState extends State { final buttonSeparator = buttonCount > 3 ? (baseButtonSeparator * 0.5).clamp(5.0, 20.0) : baseButtonSeparator; - + final buttonBarHeight = (1 - widget.shrink) < 0.7 ? 60.0 : progressiveClamp(40, 120, widget.shrink); - + // Reduce button size when there are more buttons final baseButtonSize = (1 - widget.shrink) < 0.9 ? 60.0 : 80.0; final buttonSize = buttonCount > 3 ? (baseButtonSize * 0.85).clamp(50.0, 70.0) : baseButtonSize; - + final baseButtonIconSize = (1 - widget.shrink) < 0.9 ? 20.0 : 40.0; final buttonIconSize = buttonCount > 3 ? (baseButtonIconSize * 0.85).clamp(18.0, 35.0) : baseButtonIconSize; - + final buttonFontSize = (1 - widget.shrink) < 0.9 ? 12.0 : progressiveClamp(10, buttonCount > 3 ? 12 : 14, widget.shrink); @@ -363,11 +369,13 @@ class _WalletActionsState extends State { if (showActionButton && featuredPlugin != null) ...[ Flexible( child: WalletActionButton( - key: Key('featured_plugin_action_button_${featuredPlugin.name}'), + key: Key( + 'featured_plugin_action_button_${featuredPlugin.name}'), customIcon: featuredPlugin.icon != null ? SvgPicture.network( featuredPlugin.icon!, - semanticsLabel: '${featuredPlugin.name} icon', + semanticsLabel: + '${featuredPlugin.name} icon', height: buttonIconSize, width: buttonIconSize, placeholderBuilder: (_) => Icon( @@ -390,7 +398,8 @@ class _WalletActionsState extends State { alt: true, loading: sendLoading, disabled: sendLoading, - onPressed: () => widget.handlePlugin!(featuredPlugin), + onPressed: () => + widget.handlePlugin!(featuredPlugin), ), ), SizedBox( diff --git a/lib/services/config/config.dart b/lib/services/config/config.dart index 01b02434..8bcb728c 100644 --- a/lib/services/config/config.dart +++ b/lib/services/config/config.dart @@ -370,6 +370,7 @@ class PluginConfig { final bool hidden; final bool signature; final bool featured; + final Map? meta; PluginConfig({ required this.name, @@ -380,6 +381,7 @@ class PluginConfig { this.hidden = false, this.signature = false, this.featured = false, + this.meta, }); factory PluginConfig.fromJson(Map json) { @@ -394,6 +396,7 @@ class PluginConfig { hidden: json['hidden'] ?? false, signature: json['signature'] ?? false, featured: json['featured'] ?? false, + meta: json['meta'] as Map?, ); } @@ -412,13 +415,14 @@ class PluginConfig { 'hidden': hidden, 'signature': signature, 'featured': featured, + if (meta != null) 'meta': meta, }; } // to string @override String toString() { - return 'PluginConfig{name: $name, icon: $icon, url: $url, launchMode: $launchMode, action: $action, hidden: $hidden, signature: $signature, featured: $featured}'; + return 'PluginConfig{name: $name, icon: $icon, url: $url, launchMode: $launchMode, action: $action, hidden: $hidden, signature: $signature, featured: $featured, meta: $meta}'; } } @@ -706,6 +710,10 @@ class Config { return plugins?.firstWhereOrNull((plugin) => plugin.action == 'topup'); } + PluginConfig? getOffboardPlugin() { + return plugins?.firstWhereOrNull((plugin) => plugin.action == 'offboard'); + } + TokenConfig getPrimaryToken() { final primaryToken = tokens[community.primaryToken.fullAddress]; if (primaryToken == null) { diff --git a/lib/services/config/legacy.dart b/lib/services/config/legacy.dart index 7fe9c7e0..a9c5bf4c 100644 --- a/lib/services/config/legacy.dart +++ b/lib/services/config/legacy.dart @@ -571,4 +571,8 @@ class LegacyConfig { LegacyPluginConfig? getTopUpPlugin() { return plugins.firstWhereOrNull((plugin) => plugin.action == 'topup'); } + + LegacyPluginConfig? getOffboardPlugin() { + return plugins.firstWhereOrNull((plugin) => plugin.action == 'offboard'); + } } diff --git a/lib/services/engine/events.dart b/lib/services/engine/events.dart index 076faf1e..9df5c67d 100644 --- a/lib/services/engine/events.dart +++ b/lib/services/engine/events.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io' show WebSocket; +import 'dart:io' show WebSocket, WebSocketException; import 'package:citizenwallet/utils/delay.dart'; @@ -9,6 +9,7 @@ enum EventServiceState { connecting, connected, error, + closed, } class WebSocketEvent { @@ -89,9 +90,25 @@ class EventService { onDone: _onDone, ); } catch (e) { + // Check if this is a WebSocketException with 404 status + if (e is WebSocketException) { + final fullError = e.toString(); + + // Check if the full error string contains "404" + if (fullError.contains('404')) { + // Don't reconnect on 404 - the endpoint doesn't exist + _isConnected = false; + _onStateChange(EventServiceState.closed); + return; + } + } + + // Handle other connection errors print('Connection error: $e'); + print('Error type: ${e.runtimeType}'); _isConnected = false; _onStateChange(EventServiceState.error); + Duration delay = Duration(seconds: _reconnectDelay.inSeconds); if (reconnectDelay != null && reconnectDelay >= _reconnectMaxSeconds) { delay = Duration(seconds: reconnectDelay.inSeconds); diff --git a/lib/state/wallet/logic.dart b/lib/state/wallet/logic.dart index 43f090dc..dfcae9ab 100644 --- a/lib/state/wallet/logic.dart +++ b/lib/state/wallet/logic.dart @@ -1103,7 +1103,8 @@ class WalletLogic extends WidgetsBindingObserver { final trimmedAmount = amount.trim(); if (trimmedAmount.endsWith(',') || trimmedAmount.endsWith('.')) { // Remove trailing separator and validate the partial amount - final withoutTrailing = trimmedAmount.substring(0, trimmedAmount.length - 1); + final withoutTrailing = + trimmedAmount.substring(0, trimmedAmount.length - 1); if (withoutTrailing.isEmpty) { // Just "," or "." - treat as empty (not invalid, but also not valid) return false; @@ -1118,7 +1119,7 @@ class WalletLogic extends WidgetsBindingObserver { balanceRaw, decimals: _wallet.currency.decimals, )); - + // Parse the amount as a double in human-readable format // Handle both comma and dot as decimal separators final normalizedAmount = amount.replaceAll(',', '.'); @@ -1726,7 +1727,7 @@ class WalletLogic extends WidgetsBindingObserver { Future updateAmount({bool unlimited = false}) async { // Fetch current balance before validating to ensure we check against the latest balance await updateBalance(); - + _state.setHasAmount( _amountController.text.isNotEmpty, isInvalidAmount(_amountController.value.text, unlimited: unlimited), diff --git a/lib/widgets/communities/community_closed_banner.dart b/lib/widgets/communities/community_closed_banner.dart new file mode 100644 index 00000000..5111c4c6 --- /dev/null +++ b/lib/widgets/communities/community_closed_banner.dart @@ -0,0 +1,403 @@ +import 'dart:async'; + +import 'package:citizenwallet/services/config/config.dart'; +import 'package:citizenwallet/theme/provider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:citizenwallet/l10n/app_localizations.dart'; + +// The CommunityClosedBanner class remains the same. +class CommunityClosedBanner extends StatefulWidget { + final VoidCallback? handleOffboardPlugin; + final VoidCallback? onDismiss; + final bool display; + final PluginConfig? offboardPlugin; + + const CommunityClosedBanner({ + super.key, + this.handleOffboardPlugin, + this.onDismiss, + this.display = false, + this.offboardPlugin, + }); + + @override + State createState() => _CommunityClosedBannerState(); +} + +// ==================================================================== +// Refactored State Class +// ==================================================================== + +class _CommunityClosedBannerState extends State + with SingleTickerProviderStateMixin { + // ADD this Mixin + + // Existing state variables + bool _display = false; + double _slideOffset = 100; + bool _isDismissed = false; + + // Keep these for tracking the current drag and its position + double _dragOffset = 0; + bool _isDragging = false; + + // New: Animation Controller for the drag effect (snap-back or final dismissal) + late AnimationController _dragAnimationController; + late Animation _dragAnimation; + + Timer? _showTimer; + Timer? _hideTimer; + + @override + void initState() { + super.initState(); + + _display = widget.display; + _slideOffset = widget.display ? 0 : 100; + + _dragAnimationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + )..addListener(_handleAnimationUpdate); + + // 💡 FIX: Initialize _dragAnimation here with a default value of 0 + _dragAnimation = Tween( + begin: 0, + end: 0, + ).animate(_dragAnimationController); + } + + // New: Listener for the drag animation + void _handleAnimationUpdate() { + setState(() { + // The current drag offset is controlled by the animation when not dragging + _dragOffset = _dragAnimation.value; + }); + // If the animation is completing and it was a dismissal animation, call hide + if (_dragAnimationController.isCompleted && !_isDragging && _isDismissed) { + // This hide call completes the process by setting _display = false + // and animating the main widget's slide offset back to 100 + hide(); + } + } + + @override + void didUpdateWidget(CommunityClosedBanner oldWidget) { + super.didUpdateWidget(oldWidget); + + if (_isDismissed) return; + + if (widget.display != oldWidget.display) { + if (widget.display) { + show(); + } else { + hide(); + } + } + } + + @override + void dispose() { + _showTimer?.cancel(); + _hideTimer?.cancel(); + _dragAnimationController.dispose(); // DISPOSE the controller + super.dispose(); + } + + void show() async { + if (_isDismissed) return; + + setState(() { + _display = true; + _dragOffset = 0; // Ensure reset on show + _isDragging = false; // Ensure reset on show + }); + + _hideTimer?.cancel(); + _showTimer = Timer(const Duration(milliseconds: 50), () { + HapticFeedback.heavyImpact(); + + setState(() { + _slideOffset = 0; + }); + }); + } + + void hide() async { + setState(() { + _slideOffset = 100; + }); + + _showTimer?.cancel(); + _hideTimer = Timer(const Duration(milliseconds: 400), () { + HapticFeedback.lightImpact(); + + setState(() { + _display = false; + }); + }); + } + + void handleLearnMore() { + widget.handleOffboardPlugin?.call(); + } + + void handleDismiss() { + // Only set _isDismissed and call onDismiss. The visual dismissal animation + // is now handled by _handleDragEnd/AnimatedBuilder. + setState(() { + _isDismissed = true; + }); + widget.onDismiss?.call(); + // hide() is called after the dismissal animation completes in _handleAnimationUpdate + } + + void _handleDragStart(DragStartDetails details) { + // Stop any ongoing animation before starting a new drag + _dragAnimationController.stop(); + setState(() { + _isDragging = true; + }); + } + + void _handleDragUpdate(DragUpdateDetails details) { + setState(() { + // 1. Update the drag offset based on user movement (details.delta.dy) + // Dragging down (dy > 0) increases _dragOffset. + // Dragging up (dy < 0) decreases _dragOffset. + _dragOffset += details.delta.dy * 0.8; + + // 2. ⚠️ Recommended FIX: Clamp the drag offset to prevent it from becoming negative. + // A negative _dragOffset translates the banner UP. By clamping at 0, + // we ensure the banner cannot move up past its fully displayed state. + if (_dragOffset < 0) { + _dragOffset = 0; + } + + // The previous 'if (_dragOffset < 0 && _slideOffset < 100)' block + // is no longer necessary, as the clamp handles the restriction. + // Any content related to upward drag logic (like the -50 limit) + // is also overridden by the strict clamp at 0. + }); + } + + void _handleDragEnd(DragEndDetails details) { + final velocity = details.primaryVelocity ?? 0; + final screenHeight = MediaQuery.of(context).size.height; + // Lower the threshold to make dismissal easier + final dismissThreshold = screenHeight * 0.15; // 15% of screen height + + // Calculate the target end position for the animation + final double targetEndOffset; + final bool shouldDismiss; + + if (_dragOffset > dismissThreshold || velocity > 400) { + // ⬇️ DRAG DOWN TO DISMISS + HapticFeedback.mediumImpact(); + shouldDismiss = true; + targetEndOffset = screenHeight; + } else if (_dragOffset < 0 && _dragOffset.abs() > 10) { + // ⬆️ DRAG UP (Optional: check if drag up gesture was significant) + // If you wanted a "compacted" state on drag up, this is where you'd set targetEndOffset to a small negative value. + // For now, we'll just snap back to 0. + HapticFeedback.lightImpact(); + shouldDismiss = false; + targetEndOffset = 0; // Snap back to origin + } else { + // ➖ SNAP BACK (Not enough drag in either direction) + HapticFeedback.lightImpact(); + shouldDismiss = false; + targetEndOffset = 0; + } + + // Set dragging to false + setState(() { + _isDragging = false; + }); + + if (shouldDismiss) { + handleDismiss(); // Call handleDismiss to set _isDismissed = true + } + + // Configure and start the animation for snap-back or dismissal + _dragAnimation = Tween( + begin: _dragOffset, + end: targetEndOffset, + ).animate(CurvedAnimation( + parent: _dragAnimationController, + curve: shouldDismiss + ? Curves.easeOut + : Curves.easeOutCubic, // Different curves for dismissal vs snap-back + )); + + // Reset controller and start the animation + _dragAnimationController.reset(); + _dragAnimationController.forward(); + + // Note: The final call to hide() happens in _handleAnimationUpdate + // when the dismissal animation completes. + } + + @override + Widget build(BuildContext context) { + if (!_display) { + return const SizedBox(); + } + + final screenHeight = MediaQuery.of(context).size.height; + final safeBottomPadding = MediaQuery.of(context).padding.bottom; + final bannerHeight = screenHeight - 300; + + // Use a temporary variable for the current drag offset: + // It's either the real-time drag (if dragging) or the animated value (if snapping back/dismissing) + final currentDragOffset = _isDragging ? _dragOffset : _dragAnimation.value; + + return Positioned( + bottom: 0, + left: 0, + right: 0, + child: GestureDetector( + onVerticalDragStart: _handleDragStart, + onVerticalDragUpdate: _handleDragUpdate, + onVerticalDragEnd: _handleDragEnd, + child: AnimatedContainer( + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + transform: Matrix4.translationValues(0, _slideOffset, 0), + height: bannerHeight, + child: AnimatedBuilder( + animation: _dragAnimationController, + builder: (context, child) { + return Transform.translate( + offset: Offset(0, currentDragOffset), + child: child, + ); + }, + child: Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, safeBottomPadding + 20), + decoration: BoxDecoration( + color: const Color(0xFFF5F5F0), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colors.black.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, -5), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Drag handle indicator + Container( + width: 40, + height: 4, + margin: const EdgeInsets.only(top: 8, bottom: 30), + decoration: BoxDecoration( + color: Theme.of(context).colors.black.withOpacity(0.2), + borderRadius: BorderRadius.circular(2), + ), + ), + // Content + Expanded( + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + CupertinoIcons.info_circle_fill, + size: 60, + color: Theme.of(context) + .colors + .primary + .resolveFrom(context), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + widget.offboardPlugin?.meta?['title'] + as String? ?? + AppLocalizations.of(context)!.communityClosed, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).colors.black, + ), + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Text( + widget.offboardPlugin?.meta?['desc'] as String? ?? + AppLocalizations.of(context)! + .communityClosedDescription, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .colors + .black + .withOpacity(0.7), + height: 1.5, + ), + ), + ), + const SizedBox(height: 30), + if (widget.handleOffboardPlugin != null) + CupertinoButton( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 14, + ), + color: Theme.of(context) + .colors + .primary + .resolveFrom(context), + borderRadius: BorderRadius.circular(25), + onPressed: handleLearnMore, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.offboardPlugin?.meta?['button'] + as String? ?? + AppLocalizations.of(context)!.learnMore, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).colors.white, + ), + ), + const SizedBox(width: 8), + Icon( + CupertinoIcons.arrow_right_circle_fill, + color: Theme.of(context).colors.white, + size: 20, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f7d21d5b..421acb2c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" audio_in_app: dependency: "direct main" description: @@ -161,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" cached_network_image: dependency: "direct main" description: @@ -213,10 +221,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.3" cli_config: dependency: transitive description: @@ -374,10 +382,10 @@ packages: dependency: transitive description: name: dev_build - sha256: "1d9aa167c05cbe4be9fbaf863c76dcee9bec302fb861270672beb6d6be0bc8f4" + sha256: fda8a54458b2a873a84e0cd1513f4323a1fb0599ed5455245359bc0398bad9ee url: "https://pub.dev" source: hosted - version: "1.1.3+1" + version: "1.1.2+11" ed25519_edwards: dependency: transitive description: @@ -410,6 +418,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + eth_sig_util: + dependency: transitive + description: + name: eth_sig_util + sha256: "20fdc5ce3864e70e5ade1c1cd03cce4ef01018db00adab107303f9055d26b01a" + url: "https://pub.dev" + source: hosted + version: "0.0.9" event: dependency: transitive description: @@ -430,10 +454,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" ffi: dependency: transitive description: @@ -632,10 +656,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687" + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 url: "https://pub.dev" source: hosted - version: "2.0.32" + version: "2.0.31" flutter_secure_storage: dependency: "direct main" description: @@ -706,10 +730,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -874,10 +898,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.19.0" io: dependency: transitive description: @@ -914,26 +938,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -962,10 +986,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.1" markdown: dependency: transitive description: @@ -1138,18 +1162,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16 + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" url: "https://pub.dev" source: hosted - version: "2.2.20" + version: "2.2.19" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -1178,10 +1202,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.1.0" platform: dependency: transitive description: @@ -1274,26 +1298,34 @@ packages: dependency: transitive description: name: reown_core - sha256: "8d5d14b4e8d008b09ec9db964ab8913b4fc17000d666eb1fece20a80a4b5e37a" + sha256: "37e8bd16263400856592b58331ec61665bcc8814d4a2a1801a7f12caf3c4673d" url: "https://pub.dev" source: hosted - version: "1.3.6" + version: "1.2.0" reown_sign: dependency: transitive description: name: reown_sign - sha256: c2fec55ed3d0042d0802c80d7fc36f9b1937eac6ef9c2907dae3b8146188cffa + sha256: ae2e171b93ddaae2ce7db18a66647afc9680cf55f49e88d49a3c3df5d1eb2cde url: "https://pub.dev" source: hosted - version: "1.3.7" + version: "1.2.0" reown_walletkit: dependency: "direct main" description: name: reown_walletkit - sha256: f96cc9f6e264138b3411fd1635e06d437d5912d7a21ef28af21974e2680dfd70 + sha256: b779e4914c9299edea80a849773fc08ddd5e44549bf53a2fd26cac4a59f5c99c + url: "https://pub.dev" + source: hosted + version: "1.2.0" + reown_yttrium: + dependency: transitive + description: + name: reown_yttrium + sha256: e59d9e8dbad8e2c420c201719da12bca528c38ecab9571364d5b6d18715ce773 url: "https://pub.dev" source: hosted - version: "1.3.7" + version: "0.0.1" rxdart: dependency: "direct main" description: @@ -1338,18 +1370,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "34266009473bf71d748912da4bf62d439185226c03e01e2d9687bc65bbfcb713" + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.4.13" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "1c33a907142607c40a7542768ec9badfd16293bac51da3a4482623d15845f88b" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1472,18 +1504,18 @@ packages: dependency: transitive description: name: sqflite_android - sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" url: "https://pub.dev" source: hosted - version: "2.4.2+2" + version: "2.4.1" sqflite_common: dependency: "direct main" description: name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.5" sqflite_common_ffi: dependency: transitive description: @@ -1496,10 +1528,10 @@ packages: dependency: "direct main" description: name: sqflite_common_ffi_web - sha256: "793c1ff5b0c95ac618e7731e209db99e96abff59ad3432a3c91bd2b1454a00d5" + sha256: "983cf7b33b16e6bc086c8e09f6a1fae69d34cdb167d7acaf64cbd3515942d4e6" url: "https://pub.dev" source: hosted - version: "1.0.1+2" + version: "1.0.0" sqflite_darwin: dependency: transitive description: @@ -1560,10 +1592,10 @@ packages: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.3.1" term_glyph: dependency: transitive description: @@ -1576,26 +1608,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.8" timeago: dependency: "direct main" description: @@ -1640,18 +1672,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9" + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" url: "https://pub.dev" source: hosted - version: "6.3.24" + version: "6.3.20" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9" + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 url: "https://pub.dev" source: hosted - version: "6.3.5" + version: "6.3.4" url_launcher_linux: dependency: transitive description: @@ -1664,10 +1696,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9" + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f url: "https://pub.dev" source: hosted - version: "3.2.4" + version: "3.2.3" url_launcher_platform_interface: dependency: transitive description: @@ -1736,18 +1768,18 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "14.3.1" wallet: dependency: transitive description: @@ -1808,10 +1840,10 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "5.13.0" x25519: dependency: transitive description: @@ -1832,10 +1864,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "6.5.0" yaml: dependency: transitive description: @@ -1845,5 +1877,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.7.2 <4.0.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 82726d72..c1042a95 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: citizenwallet -version: 2.0.30+290 +version: 2.0.31+292 publish_to: none description: A mobile wallet for your community. environment: