From 2cd5e613de16db466fea9dec7b5b62b24e02984d Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Thu, 11 Dec 2025 11:23:09 +0530 Subject: [PATCH 01/16] flutter packages --- ios/Podfile.lock | 74 ++++++++++----------- pubspec.lock | 168 ++++++++++++++++++++++++++++------------------- 2 files changed, 137 insertions(+), 105 deletions(-) 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/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" From fd3adffb271a7e21df27f95701596e9542b3c12e Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Thu, 11 Dec 2025 11:25:50 +0530 Subject: [PATCH 02/16] feat(events): Handle permanent closure state for EventService Introduces the `EventServiceState.closed` state to manage permanent connection termination scenarios, preventing unnecessary reconnection attempts. Key Changes: * **Events Service (`events.dart`):** * Adds `EventServiceState.closed` to the enum. * Modifies the connection error handling (`connect`) to check for a `WebSocketException` containing "404". If a 404 is detected, the service transitions to `closed` state and aborts reconnection. * **Wallet Logic (`logic.dart`):** * Adds a check in `handleInitialLoad` to immediately transition to `EventServiceState.closed` if the loaded `communityConfig.community.hidden` flag is true, preventing the service from attempting to connect to a hidden community. * **UI Cleanup (`screen.dart`):** * Removes unused state variable `eventServiceIntentionalDisconnect`. * Updates logic to calculate `isClosed` based on the new state. --- lib/screens/wallet/screen.dart | 7 ++----- lib/services/engine/events.dart | 19 ++++++++++++++++++- lib/state/wallet/logic.dart | 13 ++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index 73aaf54f..cbe29239 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -1123,7 +1123,7 @@ class WalletScreenState extends State _receiveParams = null; _deepLink = deepLink; _deepLinkParams = deepLinkParams; - + if (voucher != null && voucherParams != null) { _sendToURL = null; } else { @@ -1195,13 +1195,10 @@ 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 isClosed = eventServiceState == EventServiceState.closed; final cleaningUp = context.select((WalletState state) => state.cleaningUp); final config = context.select((WalletState state) => state.config); 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..52527446 100644 --- a/lib/state/wallet/logic.dart +++ b/lib/state/wallet/logic.dart @@ -622,6 +622,12 @@ class WalletLogic extends WidgetsBindingObserver { _eventService = null; } + // Check if community is hidden/closed + if (communityConfig.community.hidden) { + handleEventServiceStateChange(EventServiceState.closed); + return; + } + _eventService = EventService( communityConfig.chains[token.chainId.toString()]!.node.wsUrl, token.address, @@ -1103,7 +1109,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 +1125,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 +1733,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), From f5695d4d04681dc63a84f0f888ebbbb86fe176b9 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Thu, 11 Dec 2025 13:02:36 +0530 Subject: [PATCH 03/16] eval 'withOfflineBanner' with EventServiceState in (error,connecting) --- lib/screens/wallet/wallet_actions.dart | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/screens/wallet/wallet_actions.dart b/lib/screens/wallet/wallet_actions.dart index 12ae17d3..660e2acb 100644 --- a/lib/screens/wallet/wallet_actions.dart +++ b/lib/screens/wallet/wallet_actions.dart @@ -63,15 +63,15 @@ 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 blockSending = context.select(selectShouldBlockSending) || loading || @@ -121,22 +121,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 +363,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 +392,8 @@ class _WalletActionsState extends State { alt: true, loading: sendLoading, disabled: sendLoading, - onPressed: () => widget.handlePlugin!(featuredPlugin), + onPressed: () => + widget.handlePlugin!(featuredPlugin), ), ), SizedBox( From a1d9ac69281bca4b6249cb7c1288d248399c1166 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Thu, 11 Dec 2025 14:36:16 +0530 Subject: [PATCH 04/16] undo --- lib/state/wallet/logic.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/state/wallet/logic.dart b/lib/state/wallet/logic.dart index 52527446..dfcae9ab 100644 --- a/lib/state/wallet/logic.dart +++ b/lib/state/wallet/logic.dart @@ -622,12 +622,6 @@ class WalletLogic extends WidgetsBindingObserver { _eventService = null; } - // Check if community is hidden/closed - if (communityConfig.community.hidden) { - handleEventServiceStateChange(EventServiceState.closed); - return; - } - _eventService = EventService( communityConfig.chains[token.chainId.toString()]!.node.wsUrl, token.address, From f73e0bb71c47ba6b5605fa15fc240ed10349d09b Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Thu, 11 Dec 2025 15:05:53 +0530 Subject: [PATCH 05/16] disable 'send' and 'receive' if community is closed --- lib/screens/wallet/wallet_actions.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/screens/wallet/wallet_actions.dart b/lib/screens/wallet/wallet_actions.dart index 660e2acb..25bef986 100644 --- a/lib/screens/wallet/wallet_actions.dart +++ b/lib/screens/wallet/wallet_actions.dart @@ -73,14 +73,20 @@ class _WalletActionsState extends State { 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); From 03c955e5c90370658edccbca43b27ad425563d0f Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Thu, 11 Dec 2025 15:40:35 +0530 Subject: [PATCH 06/16] remove comments --- lib/screens/wallet/screen.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index cbe29239..f96593b6 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -1259,10 +1259,6 @@ class WalletScreenState extends State ), ), ), - // Positioned( - // bottom: 60, - // left: 0, - // right: 0, Align( alignment: Alignment.bottomCenter, child: Padding( From f1a6587923ddbe038ee614dcf972b917121c5113 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 12 Dec 2025 09:38:11 +0530 Subject: [PATCH 07/16] localised text --- lib/l10n/app_en.arb | 3 +++ lib/l10n/app_es.arb | 3 +++ lib/l10n/app_fr.arb | 3 +++ lib/l10n/app_localizations.dart | 18 ++++++++++++++++++ lib/l10n/app_localizations_en.dart | 9 +++++++++ lib/l10n/app_localizations_es.dart | 9 +++++++++ lib/l10n/app_localizations_fr.dart | 9 +++++++++ lib/l10n/app_localizations_nl.dart | 9 +++++++++ lib/l10n/app_nl.arb | 3 +++ 9 files changed, 66 insertions(+) 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", From d3fe3c5592ace0a8f11d9130cb072a9db5ab0695 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 12 Dec 2025 09:51:32 +0530 Subject: [PATCH 08/16] add CommunityClosedBanner to screen --- lib/screens/wallet/screen.dart | 14 +- .../communities/community_closed_banner.dart | 210 ++++++++++++++++++ 2 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 lib/widgets/communities/community_closed_banner.dart diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index f96593b6..2f8644cb 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'; @@ -1268,7 +1269,9 @@ class WalletScreenState extends State crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( - onTap: config?.online == false ? () => () : handleQRScan, + onTap: (config?.online == false || isClosed) + ? () => () + : handleQRScan, child: Container( height: 90, width: 90, @@ -1279,7 +1282,7 @@ class WalletScreenState extends State .resolveFrom(context), borderRadius: BorderRadius.circular(45), border: Border.all( - color: config?.online == false + color: (config?.online == false || isClosed) ? scanQrDisabledColor : Theme.of(context).colors.surfacePrimary, width: 3, @@ -1302,7 +1305,7 @@ class WalletScreenState extends State child: Icon( CupertinoIcons.qrcode_viewfinder, size: 60, - color: config?.online == false + color: (config?.online == false || isClosed) ? scanQrDisabledColor : Theme.of(context).colors.surfacePrimary, ), @@ -1391,6 +1394,11 @@ class WalletScreenState extends State ), ), ), + if (isClosed) + CommunityClosedBanner( + communityUrl: config?.community.url ?? '', + display: isClosed, + ), OfflineBanner( communityUrl: config?.community.url ?? '', display: isOffline, diff --git a/lib/widgets/communities/community_closed_banner.dart b/lib/widgets/communities/community_closed_banner.dart new file mode 100644 index 00000000..f59aeb98 --- /dev/null +++ b/lib/widgets/communities/community_closed_banner.dart @@ -0,0 +1,210 @@ +import 'dart:async'; + +import 'package:citizenwallet/theme/provider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:citizenwallet/l10n/app_localizations.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class CommunityClosedBanner extends StatefulWidget { + final String communityUrl; + final bool display; + + const CommunityClosedBanner({ + super.key, + required this.communityUrl, + this.display = false, + }); + + @override + State createState() => _CommunityClosedBannerState(); +} + +class _CommunityClosedBannerState extends State { + bool _display = false; + double _opacity = 0; + double _slideOffset = 100; + + Timer? _showTimer; + Timer? _hideTimer; + + @override + void initState() { + super.initState(); + + _display = widget.display; + _opacity = widget.display ? 1 : 0; + _slideOffset = widget.display ? 0 : 100; + } + + @override + void didUpdateWidget(CommunityClosedBanner oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.display != oldWidget.display) { + if (widget.display) { + show(); + } else { + hide(); + } + } + } + + @override + void dispose() { + _showTimer?.cancel(); + _hideTimer?.cancel(); + + super.dispose(); + } + + void show() async { + setState(() { + _display = true; + }); + + _hideTimer?.cancel(); + _showTimer = Timer(const Duration(milliseconds: 50), () { + HapticFeedback.heavyImpact(); + + setState(() { + _opacity = 1; + _slideOffset = 0; + }); + }); + } + + void hide() async { + setState(() { + _opacity = 0; + _slideOffset = 100; + }); + + _showTimer?.cancel(); + _hideTimer = Timer(const Duration(milliseconds: 400), () { + HapticFeedback.lightImpact(); + + setState(() { + _display = false; + }); + }); + } + + void handleLearnMore() { + final Uri uri = Uri.parse(widget.communityUrl); + launchUrl(uri, mode: LaunchMode.inAppWebView); + } + + @override + Widget build(BuildContext context) { + if (!_display) { + return const SizedBox(); + } + + final screenHeight = MediaQuery.of(context).size.height; + final safeBottomPadding = MediaQuery.of(context).padding.bottom; + + // Calculate height to cover from bottom to just below balance area + // Leaving approximately 300-350px from top for profile/balance visibility + final bannerHeight = screenHeight - 300; + + return Positioned( + bottom: 0, + left: 0, + right: 0, + child: AnimatedOpacity( + opacity: _opacity, + duration: const Duration(milliseconds: 400), + child: AnimatedContainer( + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + transform: Matrix4.translationValues(0, _slideOffset, 0), + height: bannerHeight, + padding: EdgeInsets.fromLTRB(20, 40, 20, safeBottomPadding + 20), + decoration: BoxDecoration( + color: const Color(0xFFF5F5F0), // Pearl white + 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: SingleChildScrollView( + 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: 16), + Text( + 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: 20), + child: Text( + 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: 20), + if (widget.communityUrl.isNotEmpty) + CupertinoButton( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 12, + ), + color: + Theme.of(context).colors.primary.resolveFrom(context), + borderRadius: BorderRadius.circular(25), + onPressed: handleLearnMore, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 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, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} From 7ce0157d0bbc33f83605d2b067c2ac2f034bf69b Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 12 Dec 2025 16:27:46 +0530 Subject: [PATCH 09/16] getOffboardPlugin --- lib/services/config/config.dart | 4 ++++ lib/services/config/legacy.dart | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/services/config/config.dart b/lib/services/config/config.dart index 01b02434..486226dc 100644 --- a/lib/services/config/config.dart +++ b/lib/services/config/config.dart @@ -706,6 +706,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'); + } } From d038b654e4498b11afa196958d95d654ea8e5a55 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 12 Dec 2025 16:32:21 +0530 Subject: [PATCH 10/16] CommunityClosedBanner - Enhanced Drag-to-Dismiss Experience - Responsive Dragging - Smooth Snap-Back/Dismissal: - Reduced Dismissal Threshold: - Layout Fix --- .../communities/community_closed_banner.dart | 366 ++++++++++++++---- 1 file changed, 280 insertions(+), 86 deletions(-) diff --git a/lib/widgets/communities/community_closed_banner.dart b/lib/widgets/communities/community_closed_banner.dart index f59aeb98..d1b76af2 100644 --- a/lib/widgets/communities/community_closed_banner.dart +++ b/lib/widgets/communities/community_closed_banner.dart @@ -4,15 +4,17 @@ import 'package:citizenwallet/theme/provider.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:citizenwallet/l10n/app_localizations.dart'; -import 'package:url_launcher/url_launcher.dart'; +// The CommunityClosedBanner class remains the same. class CommunityClosedBanner extends StatefulWidget { - final String communityUrl; + final VoidCallback? handleOffboardPlugin; + final VoidCallback? onDismiss; final bool display; const CommunityClosedBanner({ super.key, - required this.communityUrl, + this.handleOffboardPlugin, + this.onDismiss, this.display = false, }); @@ -20,27 +22,69 @@ class CommunityClosedBanner extends StatefulWidget { State createState() => _CommunityClosedBannerState(); } -class _CommunityClosedBannerState extends State { +// ==================================================================== +// Refactored State Class +// ==================================================================== + +class _CommunityClosedBannerState extends State + with SingleTickerProviderStateMixin { + // ADD this Mixin + + // Existing state variables bool _display = false; - double _opacity = 0; 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() { +void initState() { super.initState(); _display = widget.display; - _opacity = widget.display ? 1 : 0; _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(); @@ -54,13 +98,17 @@ class _CommunityClosedBannerState extends State { 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(); @@ -68,7 +116,6 @@ class _CommunityClosedBannerState extends State { HapticFeedback.heavyImpact(); setState(() { - _opacity = 1; _slideOffset = 0; }); }); @@ -76,7 +123,6 @@ class _CommunityClosedBannerState extends State { void hide() async { setState(() { - _opacity = 0; _slideOffset = 100; }); @@ -91,8 +137,103 @@ class _CommunityClosedBannerState extends State { } void handleLearnMore() { - final Uri uri = Uri.parse(widget.communityUrl); - launchUrl(uri, mode: LaunchMode.inAppWebView); + 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 @@ -103,104 +244,157 @@ class _CommunityClosedBannerState extends State { final screenHeight = MediaQuery.of(context).size.height; final safeBottomPadding = MediaQuery.of(context).padding.bottom; - - // Calculate height to cover from bottom to just below balance area - // Leaving approximately 300-350px from top for profile/balance visibility 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; + + // Calculate the total offset including the initial slide-in/out and the current drag/animation + final totalOffset = _slideOffset + currentDragOffset; + return Positioned( bottom: 0, left: 0, right: 0, - child: AnimatedOpacity( - opacity: _opacity, - duration: const Duration(milliseconds: 400), + child: GestureDetector( + onVerticalDragStart: _handleDragStart, + onVerticalDragUpdate: _handleDragUpdate, + onVerticalDragEnd: _handleDragEnd, child: AnimatedContainer( + // Keep AnimatedContainer for the initial slide-in/out (_slideOffset) duration: const Duration(milliseconds: 400), curve: Curves.easeOut, + // Apply the main slide-in/out offset here transform: Matrix4.translationValues(0, _slideOffset, 0), height: bannerHeight, - padding: EdgeInsets.fromLTRB(20, 40, 20, safeBottomPadding + 20), - decoration: BoxDecoration( - color: const Color(0xFFF5F5F0), // Pearl white - 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: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - CupertinoIcons.info_circle_fill, - size: 60, - color: Theme.of(context).colors.primary.resolveFrom(context), + // Wrap the content in AnimatedBuilder to handle the drag animation + child: AnimatedBuilder( + animation: _dragAnimationController, + builder: (context, child) { + // Apply the drag/snap-back/dismissal offset here + return Transform.translate( + offset: Offset(0, currentDragOffset), + child: child, + ); + }, + child: Container( + // Now the main content container + padding: EdgeInsets.fromLTRB(20, 10, 20, safeBottomPadding + 20), + decoration: BoxDecoration( + color: const Color(0xFFF5F5F0), // Pearl white + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context)!.communityClosed, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Theme.of(context).colors.black, + boxShadow: [ + BoxShadow( + color: Theme.of(context).colors.black.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, -5), ), - ), - const SizedBox(height: 12), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - 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, + ], + ), + child: Stack( + children: [ + // ... rest of the banner content remains the same + // Drag handle indicator + Positioned( + top: 8, + left: 0, + right: 0, + child: Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: + Theme.of(context).colors.black.withOpacity(0.2), + borderRadius: BorderRadius.circular(2), + ), + ), ), ), - ), - const SizedBox(height: 20), - if (widget.communityUrl.isNotEmpty) - CupertinoButton( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 12, - ), - color: - Theme.of(context).colors.primary.resolveFrom(context), - borderRadius: BorderRadius.circular(25), - onPressed: handleLearnMore, - child: Row( - mainAxisSize: MainAxisSize.min, + Padding( + padding: const EdgeInsets.only(top: 30), + 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: 16), Text( - AppLocalizations.of(context)!.learnMore, + AppLocalizations.of(context)!.communityClosed, + textAlign: TextAlign.center, style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context).colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).colors.black, ), ), - const SizedBox(width: 8), - Icon( - CupertinoIcons.arrow_right_circle_fill, - color: Theme.of(context).colors.white, - size: 20, + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + 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: 20), + if (widget.handleOffboardPlugin != null) + CupertinoButton( + padding: const EdgeInsets.symmetric( + horizontal: 30, + vertical: 12, + ), + color: Theme.of(context) + .colors + .primary + .resolveFrom(context), + borderRadius: BorderRadius.circular(25), + onPressed: handleLearnMore, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 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, + ), + ], + ), + ), ], ), ), - ], + ], + ), ), ), ), From 39434747bfe543de78e44aa03c523aed422c709b Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 12 Dec 2025 16:32:43 +0530 Subject: [PATCH 11/16] show/hide community closed banner --- lib/screens/wallet/screen.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index 2f8644cb..da4aed99 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -86,6 +86,7 @@ class WalletScreenState extends State String? _deepLinkParams; String? _sendToURL; Config? _config; + bool _isClosedBannerDismissed = false; @override void initState() { @@ -1200,6 +1201,9 @@ class WalletScreenState extends State eventServiceState == EventServiceState.connecting; final isClosed = 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); @@ -1396,7 +1400,14 @@ class WalletScreenState extends State ), if (isClosed) CommunityClosedBanner( - communityUrl: config?.community.url ?? '', + handleOffboardPlugin: offboardPlugin != null + ? () => handlePlugin(offboardPlugin) + : null, + onDismiss: () { + setState(() { + _isClosedBannerDismissed = true; + }); + }, display: isClosed, ), OfflineBanner( From 800cee0907f9564dfcb22f6bd361f9cd491c94a3 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 12 Dec 2025 16:46:20 +0530 Subject: [PATCH 12/16] hide/ show CommunityClosedBanner from screen --- lib/screens/wallet/screen.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index da4aed99..d19d0d70 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -1200,7 +1200,7 @@ class WalletScreenState extends State final isOffline = eventServiceState == EventServiceState.error || eventServiceState == EventServiceState.connecting; - final isClosed = eventServiceState == EventServiceState.closed; + final isCommunityClosed = eventServiceState == EventServiceState.closed; final offboardPlugin = context.select( (WalletState state) => state.config!.getOffboardPlugin(), ); @@ -1273,7 +1273,7 @@ class WalletScreenState extends State crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( - onTap: (config?.online == false || isClosed) + onTap: (config?.online == false || isCommunityClosed) ? () => () : handleQRScan, child: Container( @@ -1286,7 +1286,7 @@ class WalletScreenState extends State .resolveFrom(context), borderRadius: BorderRadius.circular(45), border: Border.all( - color: (config?.online == false || isClosed) + color: (config?.online == false || isCommunityClosed) ? scanQrDisabledColor : Theme.of(context).colors.surfacePrimary, width: 3, @@ -1309,7 +1309,7 @@ class WalletScreenState extends State child: Icon( CupertinoIcons.qrcode_viewfinder, size: 60, - color: (config?.online == false || isClosed) + color: (config?.online == false || isCommunityClosed) ? scanQrDisabledColor : Theme.of(context).colors.surfacePrimary, ), @@ -1398,7 +1398,7 @@ class WalletScreenState extends State ), ), ), - if (isClosed) + if (isCommunityClosed) CommunityClosedBanner( handleOffboardPlugin: offboardPlugin != null ? () => handlePlugin(offboardPlugin) @@ -1408,7 +1408,7 @@ class WalletScreenState extends State _isClosedBannerDismissed = true; }); }, - display: isClosed, + display: isCommunityClosed, ), OfflineBanner( communityUrl: config?.community.url ?? '', From 9285b036fd42a66c4fc67ee999631cbe86f5db8a Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 6 Jan 2026 09:35:27 +0530 Subject: [PATCH 13/16] feat: add meta attribute support to plugin config for customizable banner text - Add optional 'meta' field to PluginConfig class to store custom metadata - Update CommunityClosedBanner to accept PluginConfig and use meta values - Support custom title, description, and button text from plugin meta - Refactor banner layout for improved centering and visual hierarchy - Remove Stack-based layout in favor of cleaner Column structure - Add SingleChildScrollView for better overflow handling - Fall back to localized text when meta values are not provided This enables communities to customize the closed banner messaging through the offboard plugin configuration without code changes. --- lib/screens/wallet/screen.dart | 15 +- lib/services/config/config.dart | 6 +- .../communities/community_closed_banner.dart | 193 +++++++++--------- 3 files changed, 110 insertions(+), 104 deletions(-) diff --git a/lib/screens/wallet/screen.dart b/lib/screens/wallet/screen.dart index d19d0d70..de525f8e 100644 --- a/lib/screens/wallet/screen.dart +++ b/lib/screens/wallet/screen.dart @@ -1286,9 +1286,10 @@ class WalletScreenState extends State .resolveFrom(context), borderRadius: BorderRadius.circular(45), border: Border.all( - color: (config?.online == false || isCommunityClosed) - ? 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 || isCommunityClosed) - ? scanQrDisabledColor - : Theme.of(context).colors.surfacePrimary, + color: + (config?.online == false || isCommunityClosed) + ? scanQrDisabledColor + : Theme.of(context).colors.surfacePrimary, ), ), ), @@ -1409,6 +1411,7 @@ class WalletScreenState extends State }); }, display: isCommunityClosed, + offboardPlugin: offboardPlugin, ), OfflineBanner( communityUrl: config?.community.url ?? '', diff --git a/lib/services/config/config.dart b/lib/services/config/config.dart index 486226dc..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}'; } } diff --git a/lib/widgets/communities/community_closed_banner.dart b/lib/widgets/communities/community_closed_banner.dart index d1b76af2..5111c4c6 100644 --- a/lib/widgets/communities/community_closed_banner.dart +++ b/lib/widgets/communities/community_closed_banner.dart @@ -1,5 +1,6 @@ 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'; @@ -10,12 +11,14 @@ 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 @@ -47,7 +50,7 @@ class _CommunityClosedBannerState extends State Timer? _hideTimer; @override -void initState() { + void initState() { super.initState(); _display = widget.display; @@ -158,7 +161,7 @@ void initState() { }); } -void _handleDragUpdate(DragUpdateDetails details) { + void _handleDragUpdate(DragUpdateDetails details) { setState(() { // 1. Update the drag offset based on user movement (details.delta.dy) // Dragging down (dy > 0) increases _dragOffset. @@ -250,9 +253,6 @@ void _handleDragUpdate(DragUpdateDetails details) { // It's either the real-time drag (if dragging) or the animated value (if snapping back/dismissing) final currentDragOffset = _isDragging ? _dragOffset : _dragAnimation.value; - // Calculate the total offset including the initial slide-in/out and the current drag/animation - final totalOffset = _slideOffset + currentDragOffset; - return Positioned( bottom: 0, left: 0, @@ -262,27 +262,22 @@ void _handleDragUpdate(DragUpdateDetails details) { onVerticalDragUpdate: _handleDragUpdate, onVerticalDragEnd: _handleDragEnd, child: AnimatedContainer( - // Keep AnimatedContainer for the initial slide-in/out (_slideOffset) duration: const Duration(milliseconds: 400), curve: Curves.easeOut, - // Apply the main slide-in/out offset here transform: Matrix4.translationValues(0, _slideOffset, 0), height: bannerHeight, - // Wrap the content in AnimatedBuilder to handle the drag animation child: AnimatedBuilder( animation: _dragAnimationController, builder: (context, child) { - // Apply the drag/snap-back/dismissal offset here return Transform.translate( offset: Offset(0, currentDragOffset), child: child, ); }, child: Container( - // Now the main content container - padding: EdgeInsets.fromLTRB(20, 10, 20, safeBottomPadding + 20), + padding: EdgeInsets.fromLTRB(20, 10, 20, safeBottomPadding + 20), decoration: BoxDecoration( - color: const Color(0xFFF5F5F0), // Pearl white + color: const Color(0xFFF5F5F0), borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), @@ -295,102 +290,106 @@ void _handleDragUpdate(DragUpdateDetails details) { ), ], ), - child: Stack( + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - // ... rest of the banner content remains the same // Drag handle indicator - Positioned( - top: 8, - left: 0, - right: 0, - child: Center( - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: - Theme.of(context).colors.black.withOpacity(0.2), - borderRadius: BorderRadius.circular(2), - ), - ), + 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), ), ), - Padding( - padding: const EdgeInsets.only(top: 30), - 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: 16), - Text( - 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: 20), - child: Text( - 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: 20), - if (widget.handleOffboardPlugin != null) - CupertinoButton( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 12, - ), + // 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), - borderRadius: BorderRadius.circular(25), - onPressed: handleLearnMore, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.learnMore, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, + ), + 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, ), - ), - const SizedBox(width: 8), - Icon( - CupertinoIcons.arrow_right_circle_fill, - color: Theme.of(context).colors.white, - size: 20, - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ], From b640338010fec7cc3876e0aade9ec952d2d8788b Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 6 Jan 2026 14:42:17 +0530 Subject: [PATCH 14/16] Add offboard plugin for Citizen Pay migration Add new offboard plugin to wallet.pay.brussels community configuration to enable users to migrate their balance to the Citizen Pay App. The plugin launches a webview to the migration page with appropriate metadata including title, description, and button text. --- assets/config/v4/communities.json | 13 +++++++++++++ assets/config/v4/communities.test.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) 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", From 47790aba78cd92b734c34fe5192b69182bf88f87 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 6 Jan 2026 14:54:55 +0530 Subject: [PATCH 15/16] increase android min sdk version --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d16dd1c8991174cd2eb4eaa7ed587b0812e1a109 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Tue, 6 Jan 2026 15:07:56 +0530 Subject: [PATCH 16/16] build for testflight --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: