From 130d6fc4dc31551002b161c920efa72dba625abf Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Wed, 9 Apr 2025 12:03:33 +0200 Subject: [PATCH 01/46] WIP: adding notifications pgk --- app/ios/Podfile.lock | 64 +++++++++++++++++++-------------------- app/ios/Runner/Info.plist | 5 +++ app/pubspec.lock | 40 ++++++++++++++++++++++++ app/pubspec.yaml | 1 + 4 files changed, 78 insertions(+), 32 deletions(-) diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 00fe2e794..6ac352429 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -19,6 +19,8 @@ PODS: - OrderedSet (~> 6.0.3) - flutter_keyboard_visibility (0.0.1): - Flutter + - flutter_local_notifications (0.0.1): + - Flutter - flutter_pkid (0.0.1): - Flutter - GoogleDataTransport (9.4.1): @@ -83,7 +85,7 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) - - open_filex (0.0.2): + - open_file_ios (0.0.1): - Flutter - OrderedSet (6.0.3) - package_info_plus (0.4.5): @@ -91,8 +93,6 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.3.0): - - Flutter - PromisesObjC (2.4.0) - screen_brightness_ios (0.1.0): - Flutter @@ -125,15 +125,15 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_pkid (from `.symlinks/plugins/flutter_pkid/ios`) - idenfy_sdk_flutter (from `.symlinks/plugins/idenfy_sdk_flutter/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - local_auth (from `.symlinks/plugins/local_auth/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - - open_filex (from `.symlinks/plugins/open_filex/ios`) + - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sodium_libs (from `.symlinks/plugins/sodium_libs/darwin`) @@ -176,6 +176,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" flutter_keyboard_visibility: :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_pkid: :path: ".symlinks/plugins/flutter_pkid/ios" idenfy_sdk_flutter: @@ -186,14 +188,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth/ios" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/ios" - open_filex: - :path: ".symlinks/plugins/open_filex/ios" + open_file_ios: + :path: ".symlinks/plugins/open_file_ios/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" - permission_handler_apple: - :path: ".symlinks/plugins/permission_handler_apple/ios" screen_brightness_ios: :path: ".symlinks/plugins/screen_brightness_ios/ios" shared_preferences_foundation: @@ -214,46 +214,46 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 + audio_session: f08db0697111ac84ba46191b55488c0563bb29c6 Crisp: 6747c96b2b2c2a81babf1eaecd1688a65d98edd4 - crisp_chat: 0d82d8a11d2119ec4ba173611c7c15885b05332d - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 + crisp_chat: 3bdffe847beeaf0277d5d6d1473962d758af5685 + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 - flutter_pkid: 7808bd0baee7580afc1cebebf2a6f3b290e190c5 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 + flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb + flutter_pkid: 816b9b6b9e0db3bd65fb88a23a6e70e6a3e9473e GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - idenfy_sdk_flutter: 34d12f5496381ffa2a732f7a12c37e136227e4dc + idenfy_sdk_flutter: 8b8ea0804012526a6131ea0f4fff49bd40884e58 iDenfySDK: 3fdbe83ca3fe4040917dd4f3c17f73625c0d0c47 - just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd + just_audio: 6c031bb61297cf218b4462be616638e81c058e97 + local_auth: 9797b7f53f113470a9ae99c3ff005c9f19e5fb4c lottie-ios: fcb5e73e17ba4c983140b7d21095c834b3087418 MLImage: 1824212150da33ef225fbd3dc49f184cf611046c MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1 MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 - mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740 + mobile_scanner: 92e8812bf22a8f84131e2a7f9d0f44dad1a4742b nanopb: 438bc412db1928dac798aa6fd75726007be04262 - open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 + open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - screen_brightness_ios: 7437207a2a9bc56553aa10f782afecf830b4c4e2 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sodium_libs: 2ec9dede9b53c8de7e26e53d085cfe4ecc288f95 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 - wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 - webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 + screen_brightness_ios: 28c5fbdb40634de44f86025d84470158ad4df48c + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sodium_libs: d5a8c0ec38806fe1cff3caf98c8319378da0bc1d + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 + webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c PODFILE CHECKSUM: 050ff199c8e97450c391a88d64db90da96da9995 diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist index eb9ee85f4..1c528f256 100644 --- a/app/ios/Runner/Info.plist +++ b/app/ios/Runner/Info.plist @@ -86,5 +86,10 @@ io.flutter.embedded_views_preview + UIBackgroundModes + + fetch + remote-notification + diff --git a/app/pubspec.lock b/app/pubspec.lock index 03cdf7bba..f0bddfc13 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -595,6 +595,38 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: d59eeafd6df92174b1d5f68fc9d66634c97ce2e7cfe2293476236547bb19bbbd + url: "https://pub.dev" + source: hosted + version: "19.0.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "2569b973fc9d1f63a37410a9f7c1c552081226c597190cb359ef5d5762d1631c" + url: "https://pub.dev" + source: hosted + version: "9.0.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_pkid: dependency: "direct main" description: @@ -1816,6 +1848,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.0" + timezone: + dependency: transitive + description: + name: timezone + sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d + url: "https://pub.dev" + source: hosted + version: "0.10.0" timing: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 0bda59bb5..1357abb4a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -84,6 +84,7 @@ dependencies: infinite_scroll_pagination: ^4.1.0 intl_mobile_field: ^1.1.1 mobile_scanner: 5.2.3 + flutter_local_notifications: ^19.0.0 dev_dependencies: flutter_test: sdk: flutter From 2952f0edbbce72861f0862ef8303ac8729411721 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Sun, 13 Apr 2025 11:18:56 +0200 Subject: [PATCH 02/46] fix ios issue and show notification with offline nodes --- app/ios/Runner/AppDelegate.swift | 18 +++++- app/ios/Runner/Info.plist | 13 ++++- app/lib/main.dart | 30 +++++++++- .../screens/identity_verification_screen.dart | 1 + app/lib/services/background_service.dart | 29 ++++++++++ app/lib/services/nodes_check_service.dart | 55 ++++++++++++++++++ app/lib/services/notification_service.dart | 56 +++++++++++++++++++ app/pubspec.lock | 8 +++ app/pubspec.yaml | 1 + 9 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 app/lib/services/background_service.dart create mode 100644 app/lib/services/nodes_check_service.dart create mode 100644 app/lib/services/notification_service.dart diff --git a/app/ios/Runner/AppDelegate.swift b/app/ios/Runner/AppDelegate.swift index bac1a4be7..033ed23b0 100644 --- a/app/ios/Runner/AppDelegate.swift +++ b/app/ios/Runner/AppDelegate.swift @@ -1,13 +1,27 @@ import UIKit import Flutter +import flutter_local_notifications @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( + override func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.alert, .badge, .sound]) + } + + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in + GeneratedPluginRegistrant.register(with: registry) + } + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate + } + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } -} +} \ No newline at end of file diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist index 1c528f256..9151e1cfc 100644 --- a/app/ios/Runner/Info.plist +++ b/app/ios/Runner/Info.plist @@ -88,8 +88,17 @@ UIBackgroundModes - fetch - remote-notification + fetch + remote-notification + + NSUserNotificationAlertStyle + alert + + NSLocalNetworkUsageDescription + This app uses the network to monitor your node status. + + NSUserNotificationUsageDescription + This app needs permission to send you notifications when your node is offline. diff --git a/app/lib/main.dart b/app/lib/main.dart index 89ba0ff63..bb1ea6eb3 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,8 +1,12 @@ +import 'package:background_fetch/background_fetch.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:threebotlogin/helpers/globals.dart'; +import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/screens/splash_screen.dart'; +import 'package:threebotlogin/services/background_service.dart'; +import 'package:threebotlogin/services/notification_service.dart'; import 'package:threebotlogin/services/shared_preference_service.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:threebotlogin/providers/theme_provider.dart'; @@ -33,6 +37,10 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + await NotificationService().initNotification(); + + BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask); + bool initDone = await getInitDone(); String? doubleName = await getDoubleName(); @@ -44,6 +52,27 @@ Future main() async { child: MyApp(initDone: initDone, registered: registered), ), ); + + BackgroundFetch.configure( + BackgroundFetchConfig( + minimumFetchInterval: 15, + stopOnTerminate: false, + enableHeadless: true, + requiresBatteryNotLow: false, + requiresCharging: false, + requiresStorageNotLow: false, + requiredNetworkType: NetworkType.ANY, + ), + (String taskId) async { + logger.i('[BackgroundFetch] Task: $taskId'); + await checkNodeStatus(); + BackgroundFetch.finish(taskId); + }, + (String taskId) async { + logger.i('[BackgroundFetch] Timeout: $taskId'); + BackgroundFetch.finish(taskId); + }, + ); } Future setGlobalValues() async { @@ -52,7 +81,6 @@ Future setGlobalValues() async { Globals().emailVerified.value = (email['sei'] != null); Globals().phoneVerified.value = (phone['spi'] != null); - } class MyApp extends ConsumerWidget { diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index 692a1c171..f83050683 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -9,6 +9,7 @@ import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/main.dart'; import 'package:threebotlogin/screens/authentication_screen.dart'; +import 'package:threebotlogin/services/notification_service.dart'; import 'package:threebotlogin/services/open_kyc_service.dart'; import 'package:threebotlogin/services/pkid_service.dart'; import 'package:threebotlogin/services/tools_service.dart'; diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart new file mode 100644 index 000000000..40f7c0abf --- /dev/null +++ b/app/lib/services/background_service.dart @@ -0,0 +1,29 @@ +import 'package:background_fetch/background_fetch.dart'; +import 'package:threebotlogin/services/nodes_check_service.dart'; +import 'notification_service.dart'; + +void backgroundFetchHeadlessTask(HeadlessTask task) async { + final String taskId = task.taskId; + final bool timeout = task.timeout; + + if (timeout) { + BackgroundFetch.finish(taskId); + return; + } + await checkNodeStatus(); + + BackgroundFetch.finish(taskId); +} + +Future checkNodeStatus() async { + final offlineNodes = await NodeCheckService.pingNodesInBackground(); + + if (offlineNodes.isNotEmpty) { + final nodeIds = offlineNodes.map((n) => n.nodeId).join(', '); + await NotificationService().showNotification( + title: 'Node Alert 🚨', + body: 'Offline node(s): $nodeIds', + ); + } +} + diff --git a/app/lib/services/nodes_check_service.dart b/app/lib/services/nodes_check_service.dart new file mode 100644 index 000000000..c855dee38 --- /dev/null +++ b/app/lib/services/nodes_check_service.dart @@ -0,0 +1,55 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/models/farm.dart'; +import 'package:threebotlogin/models/wallet.dart'; +import 'package:threebotlogin/providers/wallets_provider.dart'; +import 'package:threebotlogin/services/gridproxy_service.dart'; +import 'package:threebotlogin/services/tfchain_service.dart'; + +class NodeCheckService { + static Future> pingNodesInBackground() async { + final container = ProviderContainer(); + try { + final walletsNotifierInstance = container.read(walletsNotifier.notifier); + + await walletsNotifierInstance.waitUntilListed(); + + final List wallets = container.read(walletsNotifier); + + final Map twinIdWallets = {}; + for (final wallet in wallets) { + final twinId = await getTwinId(wallet.tfchainSecret); + if (twinId != 0) { + twinIdWallets[twinId] = wallet; + } + } + final farmsList = await getFarmsByTwinIds(twinIdWallets.keys.toList()); + final allNodes = []; + for (final farm in farmsList) { + final nodesData = await getNodesByFarmId(farm.farmID); + + final nodes = nodesData + .map((node) => Node( + nodeId: node.nodeId, + status: NodeStatus.values.firstWhere( + (e) => + e.toString().toLowerCase() == + 'nodestatus.${node.status.toLowerCase()}', + ), + )) + .toList(); + allNodes.addAll(nodes); + } + final offlineNodes = + allNodes.where((n) => n.status != NodeStatus.Up).toList(); + + return offlineNodes; + + } catch (e) { + logger.e('[NodeCheckService] Error: $e'); + return []; + } finally { + container.dispose(); + } + } +} diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart new file mode 100644 index 000000000..3fcdf183d --- /dev/null +++ b/app/lib/services/notification_service.dart @@ -0,0 +1,56 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class NotificationService { + static final NotificationService _instance = NotificationService._internal(); + factory NotificationService() => _instance; + NotificationService._internal(); + + final notificationsPlugin = FlutterLocalNotificationsPlugin(); + bool _isInitialized = false; + + Future initNotification() async { + if (_isInitialized) return; + + const initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); + const initSettingsIOS = DarwinInitializationSettings( + requestAlertPermission: true, + requestBadgePermission: true, + requestSoundPermission: true, + ); + + const initSettings = InitializationSettings( + android: initSettingsAndroid, + iOS: initSettingsIOS, + ); + + await notificationsPlugin.initialize(initSettings); + + _isInitialized = true; + } + + NotificationDetails _notificationDetails() { + return const NotificationDetails( + android: AndroidNotificationDetails( + 'node_status_channel', + 'Node Status', + channelDescription: 'Notify user when node goes offline', + importance: Importance.max, + priority: Priority.high, + ), + iOS: DarwinNotificationDetails( + presentAlert: true, + presentSound: true, + presentBadge: true, + ), + ); + } + + Future showNotification({ + int id = 0, + required String title, + required String body, + }) async { + await notificationsPlugin.show(id, title, body, _notificationDetails()); + } +} + diff --git a/app/pubspec.lock b/app/pubspec.lock index f0bddfc13..f0caf4cda 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -70,6 +70,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.21" + background_fetch: + dependency: "direct main" + description: + name: background_fetch + sha256: "442e82f508708be89fd0cc7e1dc3b27bc7c6c8c39a47967ccb7ed1c57b9108b5" + url: "https://pub.dev" + source: hosted + version: "1.3.8" base_x: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 1357abb4a..6e5b232cc 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -85,6 +85,7 @@ dependencies: intl_mobile_field: ^1.1.1 mobile_scanner: 5.2.3 flutter_local_notifications: ^19.0.0 + background_fetch: ^1.3.8 dev_dependencies: flutter_test: sdk: flutter From 783e5800067f938d0c52b3b32bf96bd1e8c366ce Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Sun, 13 Apr 2025 11:26:03 +0200 Subject: [PATCH 03/46] remove unused import && undo changes in podfile.lock --- app/ios/Podfile.lock | 66 +++++++++---------- .../screens/identity_verification_screen.dart | 1 - 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 6ac352429..34d66e3de 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -19,8 +19,6 @@ PODS: - OrderedSet (~> 6.0.3) - flutter_keyboard_visibility (0.0.1): - Flutter - - flutter_local_notifications (0.0.1): - - Flutter - flutter_pkid (0.0.1): - Flutter - GoogleDataTransport (9.4.1): @@ -85,7 +83,7 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) - - open_file_ios (0.0.1): + - open_filex (0.0.2): - Flutter - OrderedSet (6.0.3) - package_info_plus (0.4.5): @@ -93,6 +91,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - permission_handler_apple (9.3.0): + - Flutter - PromisesObjC (2.4.0) - screen_brightness_ios (0.1.0): - Flutter @@ -125,15 +125,15 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_pkid (from `.symlinks/plugins/flutter_pkid/ios`) - idenfy_sdk_flutter (from `.symlinks/plugins/idenfy_sdk_flutter/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - local_auth (from `.symlinks/plugins/local_auth/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) + - open_filex (from `.symlinks/plugins/open_filex/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sodium_libs (from `.symlinks/plugins/sodium_libs/darwin`) @@ -176,8 +176,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" flutter_keyboard_visibility: :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_pkid: :path: ".symlinks/plugins/flutter_pkid/ios" idenfy_sdk_flutter: @@ -188,12 +186,14 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth/ios" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/ios" - open_file_ios: - :path: ".symlinks/plugins/open_file_ios/ios" + open_filex: + :path: ".symlinks/plugins/open_filex/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" screen_brightness_ios: :path: ".symlinks/plugins/screen_brightness_ios/ios" shared_preferences_foundation: @@ -214,47 +214,47 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - audio_session: f08db0697111ac84ba46191b55488c0563bb29c6 + audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 Crisp: 6747c96b2b2c2a81babf1eaecd1688a65d98edd4 - crisp_chat: 3bdffe847beeaf0277d5d6d1473962d758af5685 - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + crisp_chat: 0d82d8a11d2119ec4ba173611c7c15885b05332d + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_pkid: 816b9b6b9e0db3bd65fb88a23a6e70e6a3e9473e + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 + flutter_pkid: 7808bd0baee7580afc1cebebf2a6f3b290e190c5 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - idenfy_sdk_flutter: 8b8ea0804012526a6131ea0f4fff49bd40884e58 + idenfy_sdk_flutter: 34d12f5496381ffa2a732f7a12c37e136227e4dc iDenfySDK: 3fdbe83ca3fe4040917dd4f3c17f73625c0d0c47 - just_audio: 6c031bb61297cf218b4462be616638e81c058e97 - local_auth: 9797b7f53f113470a9ae99c3ff005c9f19e5fb4c + just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa + local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd lottie-ios: fcb5e73e17ba4c983140b7d21095c834b3087418 MLImage: 1824212150da33ef225fbd3dc49f184cf611046c MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1 MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 - mobile_scanner: 92e8812bf22a8f84131e2a7f9d0f44dad1a4742b + mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740 nanopb: 438bc412db1928dac798aa6fd75726007be04262 - open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb + open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - screen_brightness_ios: 28c5fbdb40634de44f86025d84470158ad4df48c - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sodium_libs: d5a8c0ec38806fe1cff3caf98c8319378da0bc1d - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b - wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49 - webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c + screen_brightness_ios: 7437207a2a9bc56553aa10f782afecf830b4c4e2 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sodium_libs: 2ec9dede9b53c8de7e26e53d085cfe4ecc288f95 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + uni_links: d97da20c7701486ba192624d99bffaaffcfc298a + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 + wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 + webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 PODFILE CHECKSUM: 050ff199c8e97450c391a88d64db90da96da9995 -COCOAPODS: 1.16.2 +COCOAPODS: 1.16.2 \ No newline at end of file diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index f83050683..692a1c171 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -9,7 +9,6 @@ import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/main.dart'; import 'package:threebotlogin/screens/authentication_screen.dart'; -import 'package:threebotlogin/services/notification_service.dart'; import 'package:threebotlogin/services/open_kyc_service.dart'; import 'package:threebotlogin/services/pkid_service.dart'; import 'package:threebotlogin/services/tools_service.dart'; From fd5ee16fdcc74a6c35facba724d1f95ff07bef87 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 10:20:51 +0200 Subject: [PATCH 04/46] WIP: adding permissions for android --- app/android/app/src/profile/AndroidManifest.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml index 3f356e332..22698de17 100644 --- a/app/android/app/src/profile/AndroidManifest.xml +++ b/app/android/app/src/profile/AndroidManifest.xml @@ -4,4 +4,11 @@ to allow setting breakpoints, to provide hot reload, etc. --> + + + + + + + From e7939beeda4597e074cf355ead94edcbefa818ba Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 10:36:56 +0200 Subject: [PATCH 05/46] WIP: downgrade background_fetch --- app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 6e5b232cc..4098cbe91 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -85,7 +85,7 @@ dependencies: intl_mobile_field: ^1.1.1 mobile_scanner: 5.2.3 flutter_local_notifications: ^19.0.0 - background_fetch: ^1.3.8 + background_fetch: ^1.1.8 dev_dependencies: flutter_test: sdk: flutter From b8d1983596e2118f817079f300fbd81539985f59 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 10:54:04 +0200 Subject: [PATCH 06/46] WIP: downgrade background_fetch --- app/pubspec.lock | 4 ++-- app/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/pubspec.lock b/app/pubspec.lock index f0caf4cda..438d958cf 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -74,10 +74,10 @@ packages: dependency: "direct main" description: name: background_fetch - sha256: "442e82f508708be89fd0cc7e1dc3b27bc7c6c8c39a47967ccb7ed1c57b9108b5" + sha256: "18295f0c7876a09c551cc00a88db5b8dd7cef829c974f1ac2abf00c65c466677" url: "https://pub.dev" source: hosted - version: "1.3.8" + version: "1.1.3" base_x: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 4098cbe91..7f3ca4255 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -85,7 +85,7 @@ dependencies: intl_mobile_field: ^1.1.1 mobile_scanner: 5.2.3 flutter_local_notifications: ^19.0.0 - background_fetch: ^1.1.8 + background_fetch: 1.1.3 dev_dependencies: flutter_test: sdk: flutter From d8ec42ad127823d448da11aed77251f3034397d7 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:07:37 +0200 Subject: [PATCH 07/46] clear cached background_fetch --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6013718bf..69f5ec63a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,10 @@ jobs: with: flutter-version: "3.27.2" channel: "stable" - + + - name: Clear cached background_fetch package + run: rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* + - name: Get dependencies run: flutter pub get From 09fcf793e3079470139e0c8811250783e3ec2a6d Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:25:51 +0200 Subject: [PATCH 08/46] specify ndk version --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69f5ec63a..4fce697c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,11 @@ jobs: with: flutter-version: "3.27.2" channel: "stable" - + + - name: Patch NDK version + run: | + sed -i '/android {/a \ \ \ \ ndkVersion "27.0.12077973"' android/app/build.gradle + - name: Clear cached background_fetch package run: rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* From 039d0a7b323f77ca3dda6527248cb1d31813eec8 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:27:46 +0200 Subject: [PATCH 09/46] undo nkd version setup --- .github/workflows/build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fce697c8..790142df7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,10 +24,6 @@ jobs: flutter-version: "3.27.2" channel: "stable" - - name: Patch NDK version - run: | - sed -i '/android {/a \ \ \ \ ndkVersion "27.0.12077973"' android/app/build.gradle - - name: Clear cached background_fetch package run: rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* From 79255c0c1ab279389b4191e1d716095042a91fa8 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:39:53 +0200 Subject: [PATCH 10/46] WIP: add debug logs --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 790142df7..0dfbffe4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,9 @@ jobs: - name: Get dependencies run: flutter pub get + - name: Debug background_fetch version + run: grep "background_fetch" pubspec.lock || echo "background_fetch not found" + - name: Ensure Build Script is Executable run: chmod +x build.sh From 75366682bd8491af5455ff8d10163d4a8531669a Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:49:16 +0200 Subject: [PATCH 11/46] WIP: debug background_fetch version --- .github/workflows/build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0dfbffe4d..828995ea1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,8 +30,14 @@ jobs: - name: Get dependencies run: flutter pub get - - name: Debug background_fetch version - run: grep "background_fetch" pubspec.lock || echo "background_fetch not found" + - name: Verify background_fetch version + run: | + echo "Checking version of background_fetch..." + grep -A 2 "background_fetch:" pubspec.lock || echo "background_fetch not found" + if ! grep -q 'version: "1.1.3"' pubspec.lock; then + echo "❌ background_fetch is NOT version 1.1.3. Failing build." + exit 1 + fi - name: Ensure Build Script is Executable run: chmod +x build.sh From c63e68d2a7a005ef4958f727e7c5ae2dcba9e14e Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:59:38 +0200 Subject: [PATCH 12/46] update pubspec.lock --- app/pubspec.lock | 356 +++++++++++++++++++++++------------------------ 1 file changed, 178 insertions(+), 178 deletions(-) diff --git a/app/pubspec.lock b/app/pubspec.lock index 438d958cf..07fe18313 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -34,26 +34,26 @@ packages: dependency: transitive description: name: archive - sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" + sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.5" args: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70" + sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c" url: "https://pub.dev" source: hosted - version: "1.5.5" + version: "1.5.9" async: dependency: transitive description: @@ -66,10 +66,10 @@ packages: dependency: transitive description: name: audio_session - sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" + sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" url: "https://pub.dev" source: hosted - version: "0.1.21" + version: "0.1.25" background_fetch: dependency: "direct main" description: @@ -98,10 +98,10 @@ packages: dependency: transitive description: name: bip39_mnemonic - sha256: "3ae6ed74b97a0b820e71d01b75ac4bc5b036a8bb427d5ee5827427d2872eefb0" + sha256: e36c1f1fecd938d433b8f7a1e74d5bc721e16816b59dbac5b31307f079d3b3bc url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.0.9" boolean_selector: dependency: transitive description: @@ -122,34 +122,34 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct main" description: @@ -162,10 +162,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "7.3.2" built_collection: dependency: transitive description: @@ -178,34 +178,34 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.5" cached_network_image: dependency: transitive description: name: cached_network_image - sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.1" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" characters: dependency: transitive description: @@ -226,18 +226,18 @@ packages: dependency: transitive description: name: chewie - sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144" + sha256: df6711bc3ba165ad19cb496e350250be5673327f79c61c9cc8a15088ed8007ed url: "https://pub.dev" source: hosted - version: "1.7.5" + version: "1.11.1" cli_util: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: @@ -250,10 +250,10 @@ packages: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: transitive description: @@ -274,10 +274,10 @@ packages: dependency: "direct main" description: name: crisp_chat - sha256: febd2412b87df213718d6d0308d72edc0a8638e07f6b89e3895e83785ffd092b + sha256: "21d0500a1769da6652bbf63e726058aee7fd3f70debbf00a1432b250f3aac4f3" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.5" crypto: dependency: "direct main" description: @@ -314,18 +314,18 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.8" dbus: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" decimal: dependency: "direct main" description: @@ -338,10 +338,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544 + sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da" url: "https://pub.dev" source: hosted - version: "11.2.1" + version: "11.3.0" device_info_plus_platform_interface: dependency: transitive description: @@ -354,18 +354,18 @@ packages: dependency: "direct main" description: name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0+1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "2.1.1" ed25519_edwards: dependency: transitive description: @@ -378,10 +378,10 @@ packages: dependency: transitive description: name: edwards25519 - sha256: "48e4678136e362bed9790dfb716ebe8e2f34f026bdb900b058214620672c6273" + sha256: "63e05986664ca4a7b203390a76af94b0cc43608bc2916828d7728d6b5b4c8598" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" encrypt: dependency: transitive description: @@ -394,10 +394,10 @@ packages: dependency: transitive description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" fake_async: dependency: transitive description: @@ -426,18 +426,18 @@ packages: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flagsmith: dependency: "direct main" description: name: flagsmith - sha256: "9c0723c002e8cc2782c382df2f93dcb3a19bde8dd318a5bf568013ee368cb7df" + sha256: "45c9a8e05222713972e247ae8b7ee5d8d3f1e2f1e81c735e80982f0acfd870a1" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.0.1" flutter: dependency: "direct main" description: flutter @@ -607,10 +607,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: d59eeafd6df92174b1d5f68fc9d66634c97ce2e7cfe2293476236547bb19bbbd + sha256: "33b3e0269ae9d51669957a923f2376bee96299b09915d856395af8c4238aebfa" url: "https://pub.dev" source: hosted - version: "19.0.0" + version: "19.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -648,10 +648,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" + sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.0.27" flutter_riverpod: dependency: "direct main" description: @@ -762,18 +762,18 @@ packages: dependency: transitive description: name: fwfh_webview - sha256: c0a8b664b642f40f4c252a0ab4e72c22dcd97c7fb3a7e50a6b4bdb6f63afca19 + sha256: "894aa7d98ebdc2d86d79ac2309173043dec7f102575de87bf9626ddb26104e49" url: "https://pub.dev" source: hosted - version: "0.15.3" + version: "0.15.4" glob: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" google_fonts: dependency: "direct main" description: @@ -795,7 +795,7 @@ packages: description: path: "packages/gridproxy_client" ref: development - resolved-ref: "29666eae72ffd8946b06d5393fef6457c2789c7d" + resolved-ref: "0ce3b575e2f3dcd371be2152042978113b4eadb1" url: "https://github.com/threefoldtech/tfgrid-sdk-dart" source: git version: "1.0.0" @@ -803,10 +803,10 @@ packages: dependency: "direct main" description: name: hashlib - sha256: e13e8237d93fb275cd1c55fc339bb90638994d1a4f140c7ee270173b51f3d169 + sha256: f00cbea036b8a8ea19be5df1e233fb2e250858639d3488776c32f0bd60b41cc4 url: "https://pub.dev" source: hosted - version: "1.21.1" + version: "1.21.2" hashlib_codecs: dependency: transitive description: @@ -827,10 +827,10 @@ packages: dependency: transitive description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.5+1" http: dependency: "direct main" description: @@ -843,18 +843,18 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" idenfy_sdk_flutter: dependency: "direct main" description: @@ -867,10 +867,10 @@ packages: dependency: transitive description: name: image - sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.4" infinite_scroll_pagination: dependency: "direct main" description: @@ -899,10 +899,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -931,26 +931,26 @@ packages: dependency: transitive description: name: just_audio - sha256: b41646a8241688f1d99c2e69c4da2bb26aa4b3a99795f6ff205c2a165e033fda + sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e url: "https://pub.dev" source: hosted - version: "0.9.41" + version: "0.9.46" just_audio_platform_interface: dependency: transitive description: name: just_audio_platform_interface - sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" + sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.5.0" just_audio_web: dependency: transitive description: name: just_audio_web - sha256: "9a98035b8b24b40749507687520ec5ab404e291d2b0937823ff45d92cb18d448" + sha256: "8c7e779892e180cbc9ffb5a3c52f6e90e1cbbf4a63694cc450972a7edbd2bb6d" url: "https://pub.dev" source: hosted - version: "0.4.13" + version: "0.4.15" leak_tracker: dependency: transitive description: @@ -1003,10 +1003,10 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" lottie: dependency: "direct main" description: @@ -1059,10 +1059,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" mobile_scanner: dependency: "direct main" description: @@ -1171,26 +1171,26 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.2.0" path: dependency: transitive description: @@ -1203,10 +1203,10 @@ packages: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -1219,18 +1219,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.2.16" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1291,10 +1291,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1363,26 +1363,26 @@ packages: dependency: transitive description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.4" pub_semver: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" qr: dependency: transitive description: @@ -1476,18 +1476,18 @@ packages: dependency: "direct main" description: name: screen_brightness - sha256: "99b898dae860ebe55fc872d8e300c6eafff3ee4ccb09301b90adb3f241f29874" + sha256: eca7bd9d2c3c688bcad14855361cab7097839400b6b4a56f62b7ae511c709958 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" screen_brightness_android: dependency: transitive description: name: screen_brightness_android - sha256: ff9141bed547db02233e7dd88f990ab01973a0c8a8c04ddb855c7b072f33409a + sha256: "6ba1b5812f66c64e9e4892be2d36ecd34210f4e0da8bdec6a2ea34f1aa42683e" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" screen_brightness_ios: dependency: transitive description: @@ -1532,10 +1532,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.5.3" shared_preferences_android: dependency: "direct overridden" description: @@ -1548,10 +1548,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1572,10 +1572,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: @@ -1588,24 +1588,24 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" signer: dependency: "direct overridden" description: path: "packages/signer" ref: development - resolved-ref: "29666eae72ffd8946b06d5393fef6457c2789c7d" + resolved-ref: "0ce3b575e2f3dcd371be2152042978113b4eadb1" url: "https://github.com/threefoldtech/tfgrid-sdk-dart.git" source: git version: "0.1.0" @@ -1626,10 +1626,10 @@ packages: dependency: "direct main" description: name: smooth_page_indicator - sha256: "3b28b0c545fa67ed9e5997d9f9720d486f54c0c607e056a1094544e36934dff3" + sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c url: "https://pub.dev" source: hosted - version: "1.2.0+3" + version: "1.2.1" socket_io_client: dependency: "direct main" description: @@ -1658,10 +1658,10 @@ packages: dependency: "direct main" description: name: sodium_libs - sha256: "4e5d96a7805369dd51b24975a9072c51433466ae5716a33cff70316a14d97af9" + sha256: "7dd5324ec78ec08699f4fa89c954cc8fed93eb4a0fb1439e0beee2934f3aab21" url: "https://pub.dev" source: hosted - version: "3.4.3+1" + version: "3.4.3+2" source_span: dependency: transitive description: @@ -1730,10 +1730,10 @@ packages: dependency: transitive description: name: ss58 - sha256: "74dce6fbfbac9dba5f87b8f104ed2b7253c6aeac39d508f088044cf4827d9cd7" + sha256: ad12bcdc909e73648aba52754b1eab81880bd2cbc4fc6cbaa02695affe49201d url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.1" stack_trace: dependency: transitive description: @@ -1755,7 +1755,7 @@ packages: description: path: "packages/stellar_client" ref: development - resolved-ref: f3afde66922dfbe836d0915fac8e152c5817626b + resolved-ref: "0ce3b575e2f3dcd371be2152042978113b4eadb1" url: "https://github.com/threefoldtech/tfgrid-sdk-dart" source: git version: "0.1.0" @@ -1763,10 +1763,10 @@ packages: dependency: "direct main" description: name: stellar_flutter_sdk - sha256: dac5c9d4874ec8bb93541328718870a683dfb74d5f31c411f39b85e14cd70e79 + sha256: "25fc0f65638ac850adb95497555d568a24d5bcab87980f259013e0edaa0cceba" url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.9.3" stream_channel: dependency: transitive description: @@ -1779,10 +1779,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -1795,10 +1795,10 @@ packages: dependency: transitive description: name: strobe - sha256: a23adf7d305e45c6f210270aa925d8b404819144e29f7c5257a67281b63e0887 + sha256: "6b83c186190b07b9f98f85d44f72369a86ee6e8320e9db96271bce80d251b0d3" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" substrate_bip39: dependency: transitive description: @@ -1844,7 +1844,7 @@ packages: description: path: "packages/tfchain_client" ref: development - resolved-ref: "29666eae72ffd8946b06d5393fef6457c2789c7d" + resolved-ref: "0ce3b575e2f3dcd371be2152042978113b4eadb1" url: "https://github.com/threefoldtech/tfgrid-sdk-dart" source: git version: "0.1.0" @@ -1868,10 +1868,10 @@ packages: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" toml: dependency: transitive description: @@ -1884,10 +1884,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" uni_links: dependency: "direct main" description: @@ -1948,34 +1948,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.15" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.3" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1988,18 +1988,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" utility: dependency: transitive description: @@ -2028,18 +2028,18 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.1.15" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: @@ -2060,42 +2060,42 @@ packages: dependency: transitive description: name: video_player - sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + sha256: "7d78f0cfaddc8c19d4cb2d3bebe1bfef11f2103b0a03e5398b303a1bf65eeb14" url: "https://pub.dev" source: hosted - version: "2.9.2" + version: "2.9.5" video_player_android: dependency: transitive description: name: video_player_android - sha256: e343701aa890b74a863fa460f5c0e628127ed06a975d7d9af6b697133fb25bdf + sha256: ae7d4f1b41e3ac6d24dd9b9d5d6831b52d74a61bdd90a7a6262a33d8bb97c29a url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.8.2" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: cd5ab8a8bc0eab65ab0cea40304097edc46da574c8c1ecdee96f28cd8ef3792f + sha256: "84b4752745eeccb6e75865c9aab39b3d28eb27ba5726d352d45db8297fbd75bc" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.7.0" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "6.3.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" + sha256: "3ef40ea6d72434edbfdba4624b90fd3a80a0740d260667d91e7ecd2d79e13476" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.4" vm_service: dependency: transitive description: @@ -2108,10 +2108,10 @@ packages: dependency: transitive description: name: wakelock_plus - sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e" + sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354 url: "https://pub.dev" source: hosted - version: "1.2.10" + version: "1.2.11" wakelock_plus_platform_interface: dependency: transitive description: @@ -2124,18 +2124,18 @@ packages: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket_channel: dependency: transitive description: @@ -2156,10 +2156,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "5568f17a9c25c0fdd0737900fa1c2d1fee2d780bc212d9aec10c2d1f48ef0f59" + sha256: e09150b28a07933839adef0e4a088bb43e8c8d9e6b93025b01882d4067a58ab0 url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "4.3.4" webview_flutter_platform_interface: dependency: transitive description: @@ -2172,18 +2172,18 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" + sha256: c14455137ce60a68e1ccaf4e8f2dae8cebcb3465ddaa2fcfb57584fb7c5afe4d url: "https://pub.dev" source: hosted - version: "3.17.0" + version: "3.18.5" win32: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.10.1" win32_registry: dependency: transitive description: @@ -2220,10 +2220,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: dart: ">=3.6.0 <4.0.0" flutter: ">=3.27.0" From 62ac54d914a9b12863194c70d9ecec83d20ac7f0 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 12:18:04 +0200 Subject: [PATCH 13/46] update build.gradle --- app/android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/android/build.gradle b/app/android/build.gradle index 0cf2fae20..b05c3ee57 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -5,6 +5,7 @@ allprojects { mavenCentral() maven { url 'https://jitpack.io' } maven { url 'https://maven.google.com' } + maven { url "https://plugins.gradle.org/m2/" } } } From e3eeef06e30aa76fc07587842e250bc71ef65466 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 12:36:13 +0200 Subject: [PATCH 14/46] WIP: remove gradle cache --- .github/workflows/build.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 828995ea1..756d1ccb4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,21 +24,15 @@ jobs: flutter-version: "3.27.2" channel: "stable" - - name: Clear cached background_fetch package - run: rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* + - name: Clear Gradle and background_fetch cache + run: | + rm -rf ~/.gradle + rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* + ./gradlew clean - name: Get dependencies run: flutter pub get - - name: Verify background_fetch version - run: | - echo "Checking version of background_fetch..." - grep -A 2 "background_fetch:" pubspec.lock || echo "background_fetch not found" - if ! grep -q 'version: "1.1.3"' pubspec.lock; then - echo "❌ background_fetch is NOT version 1.1.3. Failing build." - exit 1 - fi - - name: Ensure Build Script is Executable run: chmod +x build.sh From e79387a1b0cf9d928d0cee6434b060ea388e457a Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 12:39:01 +0200 Subject: [PATCH 15/46] fix command --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 756d1ccb4..d9ab41d3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,12 +24,12 @@ jobs: flutter-version: "3.27.2" channel: "stable" - - name: Clear Gradle and background_fetch cache + - name: Clean Gradle & Flutter build cache run: | rm -rf ~/.gradle rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* - ./gradlew clean - + flutter clean + - name: Get dependencies run: flutter pub get From d3aa5a176f10803e5dd4307841a7debac136189f Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Mon, 14 Apr 2025 16:09:55 +0200 Subject: [PATCH 16/46] WIP: Update build.gradle --- app/android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/android/build.gradle b/app/android/build.gradle index b05c3ee57..c961b44f4 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -6,6 +6,7 @@ allprojects { maven { url 'https://jitpack.io' } maven { url 'https://maven.google.com' } maven { url "https://plugins.gradle.org/m2/" } + maven { url "${project(':background_fetch').projectDir}/libs"} } } From 940a189a11140c4c0b1ac9fbe2ab38c7e0bdddb9 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 16:41:04 +0200 Subject: [PATCH 17/46] WIP: fix space --- app/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/android/build.gradle b/app/android/build.gradle index c961b44f4..c1c86a29c 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -6,7 +6,7 @@ allprojects { maven { url 'https://jitpack.io' } maven { url 'https://maven.google.com' } maven { url "https://plugins.gradle.org/m2/" } - maven { url "${project(':background_fetch').projectDir}/libs"} + maven { url "${project(':background_fetch').projectDir}/libs"} } } From 2f744673f79c75212c96d919875f95a41173ae04 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 16:53:36 +0200 Subject: [PATCH 18/46] undo latest commit --- app/android/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/app/android/build.gradle b/app/android/build.gradle index c1c86a29c..b05c3ee57 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -6,7 +6,6 @@ allprojects { maven { url 'https://jitpack.io' } maven { url 'https://maven.google.com' } maven { url "https://plugins.gradle.org/m2/" } - maven { url "${project(':background_fetch').projectDir}/libs"} } } From c5d36b434dcbef2a263a1e768bbd87063c663a25 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 17:06:49 +0200 Subject: [PATCH 19/46] add maven url in build.gradle --- app/android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/android/build.gradle b/app/android/build.gradle index b05c3ee57..9b2b46c3d 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -6,6 +6,7 @@ allprojects { maven { url 'https://jitpack.io' } maven { url 'https://maven.google.com' } maven { url "https://plugins.gradle.org/m2/" } + maven {url 'https://maven.transistorsoft.com/repo'} } } From 58933f1a8b870d30cdf81670ce70185e673d9452 Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Wed, 9 Apr 2025 11:28:54 +0200 Subject: [PATCH 20/46] Add close button for confirmation dialog (#976) * add close button for confirmation dialog * add cancel btn in bridge also --- .../widgets/wallets/bridge_confirmation.dart | 21 +++++++++++ .../widgets/wallets/send_confirmation.dart | 37 +++++++++++++++---- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/app/lib/widgets/wallets/bridge_confirmation.dart b/app/lib/widgets/wallets/bridge_confirmation.dart index c2367d63e..4085be56a 100644 --- a/app/lib/widgets/wallets/bridge_confirmation.dart +++ b/app/lib/widgets/wallets/bridge_confirmation.dart @@ -165,6 +165,27 @@ class _BridgeConfirmationWidgetState extends State { ), ), ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + ), + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + ), ]), ), ); diff --git a/app/lib/widgets/wallets/send_confirmation.dart b/app/lib/widgets/wallets/send_confirmation.dart index e9bfc2dab..1eecb10a2 100644 --- a/app/lib/widgets/wallets/send_confirmation.dart +++ b/app/lib/widgets/wallets/send_confirmation.dart @@ -50,10 +50,10 @@ class _SendConfirmationWidgetState extends State { amountController.text = widget.amount; feeController.text = widget.chainType == ChainType.Stellar ? '0.1' : '0.01'; if (widget.memo != null) { - memoController.text = widget.memo!; - } else if (widget.memoHash != null) { - memoController.text = widget.memoHash!; - } + memoController.text = widget.memo!; + } else if (widget.memoHash != null) { + memoController.text = widget.memoHash!; + } super.initState(); } @@ -178,6 +178,27 @@ class _SendConfirmationWidgetState extends State { ), ), ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + ), + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ), + ), ]), ), ); @@ -218,9 +239,11 @@ class _SendConfirmationWidgetState extends State { Future _performTransfer() async { if (widget.chainType == ChainType.Stellar) { - final memoHash = widget.memoHash != null ? Uint8List.fromList(hex.decode(memoController.text.trim())) : null; - await Stellar.transfer( - widget.secret, widget.to, widget.amount, memo: widget.memo, memoHash: memoHash); + final memoHash = widget.memoHash != null + ? Uint8List.fromList(hex.decode(memoController.text.trim())) + : null; + await Stellar.transfer(widget.secret, widget.to, widget.amount, + memo: widget.memo, memoHash: memoHash); } else { await TFChain.transfer(widget.secret, widget.to, widget.amount); } From a71935156a7d06fba18dcea5273305a0c49afb2d Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Wed, 9 Apr 2025 11:29:25 +0200 Subject: [PATCH 21/46] Handle non-loading for delete wallet (#973) * add timeout 1 min for deleting wallet * update error messages --- app/lib/screens/wallets/wallet_info.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/lib/screens/wallets/wallet_info.dart b/app/lib/screens/wallets/wallet_info.dart index d05892396..cc2465e49 100644 --- a/app/lib/screens/wallets/wallet_info.dart +++ b/app/lib/screens/wallets/wallet_info.dart @@ -47,15 +47,23 @@ class _WalletDetailsWidgetState extends ConsumerState { Future _deleteWallet() async { try { - await deleteWallet(walletNameController.text); + await Future.any([ + deleteWallet(walletNameController.text), + Future.delayed(const Duration(minutes: 1), () { + throw TimeoutException('Transfer timed out. Please try again.'); + }) + ]); await walletsRef.removeWallet(walletNameController.text); return true; } catch (e) { logger.e('Failed to delete wallet due to $e'); if (context.mounted) { + String errorMessage = e is TimeoutException + ? 'Transfer timed out. Please try again.' + : 'Failed to delete.'; final loadingFarmsFailure = SnackBar( content: Text( - 'Failed to delete', + errorMessage, style: Theme.of(context) .textTheme .bodyMedium! From bd2625d255e93a6f42498edd934abc2f3b307e7a Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Wed, 9 Apr 2025 11:29:58 +0200 Subject: [PATCH 22/46] Prevent PIN Entry Attempts During 30 Second Lockout Period (#989) * keep user locked after the many attempts * update pin screen to include live counter --- app/lib/screens/authentication_screen.dart | 58 +++++++- app/lib/widgets/pin_code.dart | 150 +++++++++++++++------ 2 files changed, 163 insertions(+), 45 deletions(-) diff --git a/app/lib/screens/authentication_screen.dart b/app/lib/screens/authentication_screen.dart index 4f7ce7e49..dae188239 100644 --- a/app/lib/screens/authentication_screen.dart +++ b/app/lib/screens/authentication_screen.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:threebotlogin/events/close_auth_event.dart'; import 'package:threebotlogin/events/events.dart'; import 'package:threebotlogin/helpers/globals.dart'; @@ -27,11 +28,17 @@ class AuthenticationScreenState extends State { int timeout = 30000; Globals globals = Globals(); late Timer timer; + bool isLocked = false; + bool _initializing = true; @override initState() { super.initState(); + _init(); + } + Future _init() async { + await _checkPersistentLock(); Events().onEvent(CloseAuthEvent().runtimeType, (CloseAuthEvent event) { if (mounted) { close(); @@ -61,6 +68,9 @@ class AuthenticationScreenState extends State { if (created != null && ((currentTimestamp - created) / 1000) > Globals().loginTimeout) { timer.cancel(); + setState(() { + isLocked = true; + }); await showDialog( context: context, @@ -84,6 +94,22 @@ class AuthenticationScreenState extends State { } } + Future _checkPersistentLock() async { + final prefs = await SharedPreferences.getInstance(); + final lockedUntil = prefs.getInt('locked_until'); + final currentTime = DateTime.now().millisecondsSinceEpoch; + + bool shouldBeLocked = lockedUntil != null && lockedUntil > currentTime; + + globals.tooManyAuthenticationAttempts = shouldBeLocked; + globals.lockedUntill = shouldBeLocked ? lockedUntil : 0; + + setState(() { + isLocked = shouldBeLocked; + _initializing = false; + }); + } + close() { if (Navigator.canPop(context)) { Navigator.pop(context, false); @@ -106,6 +132,11 @@ class AuthenticationScreenState extends State { @override Widget build(BuildContext context) { + if (_initializing) { + return const Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } return PincodeWidget( title: 'Authentication', userMessage: widget.userMessage, @@ -113,20 +144,32 @@ class AuthenticationScreenState extends State { ); } - validate(String pin) { + Future validate(String pin) async { int currentTime = DateTime.now().millisecondsSinceEpoch; - if (globals.incorrectPincodeAttempts >= 3 && - (globals.tooManyAuthenticationAttempts && - globals.lockedUntill < currentTime)) { + if (globals.tooManyAuthenticationAttempts && + globals.lockedUntill < currentTime) { globals.tooManyAuthenticationAttempts = false; globals.lockedUntill = 0; globals.incorrectPincodeAttempts = 0; + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('locked_until'); + setState(() { + isLocked = false; + }); + } + + if (_initializing || isLocked || (globals.lockedUntill > currentTime)) { + return; } if (pin == widget.correctPin && !globals.tooManyAuthenticationAttempts) { globals.incorrectPincodeAttempts = 0; - Navigator.pop(context, pin == widget.correctPin); + globals.tooManyAuthenticationAttempts = false; + globals.lockedUntill = 0; + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('locked_until'); + Navigator.pop(context, true); return; } @@ -142,6 +185,11 @@ class AuthenticationScreenState extends State { if (!globals.tooManyAuthenticationAttempts) { globals.tooManyAuthenticationAttempts = true; globals.lockedUntill = currentTime + timeout; + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt('locked_until', globals.lockedUntill); + setState(() { + isLocked = true; + }); } dialog = CustomDialog( diff --git a/app/lib/widgets/pin_code.dart b/app/lib/widgets/pin_code.dart index d77fbc96f..4135237ee 100644 --- a/app/lib/widgets/pin_code.dart +++ b/app/lib/widgets/pin_code.dart @@ -1,5 +1,8 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:pinput/pinput.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:threebotlogin/helpers/globals.dart'; class PincodeWidget extends StatefulWidget { @@ -14,7 +17,6 @@ class PincodeWidget extends StatefulWidget { final String userMessage; final bool hideBackButton; final Function(String) handler; - @override State createState() => _PincodeWidgetState(); } @@ -24,14 +26,69 @@ class _PincodeWidgetState extends State { final pinController = TextEditingController(); final focusNode = FocusNode(); final formKey = GlobalKey(); + Timer? _countdownTimer; + int _remainingSeconds = -1; + + @override + void initState() { + super.initState(); + _checkLockRemainingTime(); + } @override void dispose() { pinController.dispose(); focusNode.dispose(); + _countdownTimer?.cancel(); super.dispose(); } + Future _checkLockRemainingTime() async { + final prefs = await SharedPreferences.getInstance(); + final lockedUntil = prefs.getInt('locked_until'); + final currentTime = DateTime.now().millisecondsSinceEpoch; + + if (lockedUntil != null && lockedUntil > currentTime) { + final remainingMs = lockedUntil - currentTime; + _startCountdown((remainingMs / 1000).ceil()); + } else { + setState(() { + _remainingSeconds = 0; + }); + } + } + + void _startCountdown(int seconds) { + setState(() { + _remainingSeconds = seconds; + }); + + _countdownTimer?.cancel(); + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_remainingSeconds > 1) { + setState(() { + _remainingSeconds--; + }); + } else { + timer.cancel(); + setState(() { + _remainingSeconds = 0; + }); + } + }); + } + + Future checkAndStartCountdown() async { + final prefs = await SharedPreferences.getInstance(); + final lockedUntil = prefs.getInt('locked_until'); + final currentTime = DateTime.now().millisecondsSinceEpoch; + + if (lockedUntil != null && lockedUntil > currentTime) { + final remainingMs = lockedUntil - currentTime; + _startCountdown((remainingMs / 1000).ceil()); + } + } + @override Widget build(BuildContext context) { final focusedBorderColor = Theme.of(context).colorScheme.primary; @@ -49,8 +106,6 @@ class _PincodeWidgetState extends State { border: Border.all(color: borderColor), ), ); - - /// Optionally you can use form to validate the Pinput return Scaffold( appBar: AppBar( elevation: 0, @@ -62,6 +117,17 @@ class _PincodeWidgetState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + if (_remainingSeconds > 0) + Padding( + padding: const EdgeInsets.only(top: 16), + child: Text( + 'Try again in $_remainingSeconds seconds', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).colorScheme.error), + ), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( @@ -73,47 +139,51 @@ class _PincodeWidgetState extends State { ), ), const SizedBox(height: 100), - Pinput( - autofocus: true, - obscureText: true, - controller: pinController, - focusNode: focusNode, - defaultPinTheme: defaultPinTheme, - separatorBuilder: (index) => const SizedBox(width: 8), - onCompleted: (value) { - widget.handler(value); - pinController.clear(); - }, - hapticFeedbackType: HapticFeedbackType.lightImpact, - cursor: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - margin: const EdgeInsets.only(bottom: 9), - width: 22, - height: 1, - color: focusedBorderColor, + IgnorePointer( + ignoring: _remainingSeconds != 0, + child: Pinput( + autofocus: _remainingSeconds == 0, + obscureText: true, + controller: pinController, + focusNode: focusNode, + defaultPinTheme: defaultPinTheme, + separatorBuilder: (index) => const SizedBox(width: 8), + onCompleted: (value) { + widget.handler(value); + pinController.clear(); + checkAndStartCountdown(); + }, + hapticFeedbackType: HapticFeedbackType.lightImpact, + cursor: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + margin: const EdgeInsets.only(bottom: 9), + width: 22, + height: 1, + color: focusedBorderColor, + ), + ], + ), + focusedPinTheme: defaultPinTheme.copyWith( + decoration: defaultPinTheme.decoration!.copyWith( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: focusedBorderColor), ), - ], - ), - focusedPinTheme: defaultPinTheme.copyWith( - decoration: defaultPinTheme.decoration!.copyWith( - borderRadius: BorderRadius.circular(8), - border: Border.all(color: focusedBorderColor), ), - ), - submittedPinTheme: defaultPinTheme.copyWith( - decoration: defaultPinTheme.decoration!.copyWith( - color: fillColor, - borderRadius: BorderRadius.circular(19), - border: Border.all(color: focusedBorderColor), + submittedPinTheme: defaultPinTheme.copyWith( + decoration: defaultPinTheme.decoration!.copyWith( + color: fillColor, + borderRadius: BorderRadius.circular(19), + border: Border.all(color: focusedBorderColor), + ), + ), + errorPinTheme: defaultPinTheme.copyBorderWith( + border: + Border.all(color: Theme.of(context).colorScheme.error), ), ), - errorPinTheme: defaultPinTheme.copyBorderWith( - border: - Border.all(color: Theme.of(context).colorScheme.error), - ), - ), + ) ], ), ), From d0cdb3641d64f1ea6a76df6eff0f14473f1114d9 Mon Sep 17 00:00:00 2001 From: Zainab Elgohary <40770501+zaelgohary@users.noreply.github.com> Date: Wed, 9 Apr 2025 12:37:51 +0200 Subject: [PATCH 23/46] Fix transactions paginations (#992) * Fix transactions pagination, use pagingToken instead of offset * Edit listTransactions to return stream instead of list * Update stellar client ref --- app/lib/screens/wallets/transactions.dart | 58 ++++++++++++++--------- app/lib/services/stellar_service.dart | 14 ++++-- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/app/lib/screens/wallets/transactions.dart b/app/lib/screens/wallets/transactions.dart index 3f681e548..002888540 100644 --- a/app/lib/screens/wallets/transactions.dart +++ b/app/lib/screens/wallets/transactions.dart @@ -18,35 +18,49 @@ class WalletTransactionsWidget extends StatefulWidget { class _WalletTransactionsWidgetState extends State { final _pageSize = 10; - final PagingController _pagingController = - PagingController(firstPageKey: 1); // Start from page 1 + final PagingController _pagingController = + PagingController(firstPageKey: null); - Future _listTransactions(int pageKey) async { + void _listTransactions(String? pagingToken) async { try { - final offset = (pageKey - 1) * _pageSize; - final txs = await listTransactions( - widget.wallet.stellarSecret, offset, _pageSize); - final isLastPage = txs.length < _pageSize; - if (isLastPage) { + final txStream = listTransactions( + widget.wallet.stellarSecret, + pagingToken, + _pageSize, + ); + + final txs = await txStream.take(_pageSize).toList(); + + if (txs.isEmpty) { + _pagingController.appendLastPage([]); + return; + } + + final lastTx = txs.last as PaymentTransaction; + final adjustedPagingToken = + (BigInt.parse(lastTx.pagingToken) - BigInt.one).toString(); + + if (txs.length < _pageSize) { _pagingController.appendLastPage(txs); } else { - _pagingController.appendPage(txs, pageKey + 1); + _pagingController.appendPage(txs, adjustedPagingToken); } } catch (e) { - logger.e('Failed to load transactions due to $e'); - _pagingController.error(e); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - 'Failed to load transaction', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Theme.of(context).colorScheme.errorContainer), + logger.e('Failed to load transactions: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Failed to load transactions', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.errorContainer, + ), + ), + duration: const Duration(seconds: 3), ), - duration: const Duration(seconds: 3), - )); + ); } + _pagingController.error = e; } } @@ -78,7 +92,7 @@ class _WalletTransactionsWidgetState extends State { ); } - return PagedListView( + return PagedListView( pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) => Column( diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index ac448ae4e..262b27099 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -46,11 +46,17 @@ Future getBalance(String secret) async { return getBalanceByClient(client); } -Future> listTransactions( - String secret, int offset, int limit) async { +Stream listTransactions( + String secret, String? pagingToken, int limit) async* { final client = Client(NetworkType.PUBLIC, secret); - return await client.getTransactions( - assetCodeFilter: 'TFT', limit: limit, offset: offset); + + await for (var response in client.getTransactions( + assetCodeFilter: 'TFT', + limit: limit, + pagingToken: pagingToken, + )) { + yield response; + } } Future?> listVestedAccounts(String secret) async { From f8d3766ba01afc501e76e262963b084bc9c5975e Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Mon, 14 Apr 2025 13:49:22 +0200 Subject: [PATCH 24/46] handle duplicated daily (#986) --- app/lib/models/wallet.dart | 15 ++++++++++++++- app/lib/services/wallet_service.dart | 10 ++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/lib/models/wallet.dart b/app/lib/models/wallet.dart index 497e61aff..28eaaec3c 100644 --- a/app/lib/models/wallet.dart +++ b/app/lib/models/wallet.dart @@ -26,7 +26,7 @@ class Wallet { String stellarBalance; String tfchainBalance; final WalletType type; - VerificationState verificationStatus; + VerificationState verificationStatus; } class PkidWallet { @@ -52,4 +52,17 @@ class PkidWallet { toMap() { return {'name': name, 'index': index, 'seed': seed, 'type': type.name}; } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PkidWallet && + runtimeType == other.runtimeType && + name == other.name && + index == other.index && + seed == other.seed && + type == other.type; + + @override + int get hashCode => Object.hash(name, index, seed, type); } diff --git a/app/lib/services/wallet_service.dart b/app/lib/services/wallet_service.dart index 4c69aa1c1..eca977a58 100644 --- a/app/lib/services/wallet_service.dart +++ b/app/lib/services/wallet_service.dart @@ -32,7 +32,7 @@ Future> getPkidWallets() async { try { pKidResult = await client.getPKidDoc('purse'); if (pKidResult.containsKey('error')) { - if (pKidResult.containsValue('Key is not found')){ + if (pKidResult.containsValue('Key is not found')) { return []; } logger.e('Error in pKidResult : ${pKidResult['error']}'); @@ -64,7 +64,8 @@ Future> listWallets() async { final List wallets = await compute((void _) async { final List> walletFutures = []; for (final w in pkidWallets) { - final walletFuture = loadWallet(w.name, w.seed, w.type, chainUrl, idenfyServiceUrl); + final walletFuture = + loadWallet(w.name, w.seed, w.type, chainUrl, idenfyServiceUrl); walletFutures.add(walletFuture); } return await Future.wait(walletFutures); @@ -121,8 +122,9 @@ Future loadWallet(String walletName, String walletSeed, final stellarBalance = balances.first.toString(); final tfchainBalance = balances.last.toString() == '0.0' ? '0' : balances.last.toString(); - final kycVerified = - await getVerificationStatus(address: tfchainClient.keypair!.address,idenfyServiceUrl: idenfyServiceUrl ); + final kycVerified = await getVerificationStatus( + address: tfchainClient.keypair!.address, + idenfyServiceUrl: idenfyServiceUrl); final wallet = Wallet( name: walletName, stellarSecret: stellarClient.secretSeed, From 9dfcd94761835a8342fa4ea95d86eb6a2050d90f Mon Sep 17 00:00:00 2001 From: AhmedHanafy725 Date: Tue, 15 Apr 2025 16:30:03 +0200 Subject: [PATCH 25/46] Update the packages version for background_fetch & flutter_local_notifications --- app/pubspec.lock | 4 ++-- app/pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/pubspec.lock b/app/pubspec.lock index 07fe18313..8523ff421 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -74,10 +74,10 @@ packages: dependency: "direct main" description: name: background_fetch - sha256: "18295f0c7876a09c551cc00a88db5b8dd7cef829c974f1ac2abf00c65c466677" + sha256: "442e82f508708be89fd0cc7e1dc3b27bc7c6c8c39a47967ccb7ed1c57b9108b5" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.3.8" base_x: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 7f3ca4255..ca6e04ef8 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -84,8 +84,8 @@ dependencies: infinite_scroll_pagination: ^4.1.0 intl_mobile_field: ^1.1.1 mobile_scanner: 5.2.3 - flutter_local_notifications: ^19.0.0 - background_fetch: 1.1.3 + flutter_local_notifications: ^19.1.0 + background_fetch: ^1.3.8 dev_dependencies: flutter_test: sdk: flutter From 6a60a0246812fb41ba31fd5a54fb02a0b5ea04af Mon Sep 17 00:00:00 2001 From: AhmedHanafy725 Date: Tue, 15 Apr 2025 16:31:32 +0200 Subject: [PATCH 26/46] Request to enable the notification service on android --- app/lib/services/notification_service.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index 3fcdf183d..fa94d88d4 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -11,7 +11,8 @@ class NotificationService { Future initNotification() async { if (_isInitialized) return; - const initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); + const initSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); const initSettingsIOS = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, @@ -24,6 +25,10 @@ class NotificationService { ); await notificationsPlugin.initialize(initSettings); + await notificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(); _isInitialized = true; } @@ -53,4 +58,3 @@ class NotificationService { await notificationsPlugin.show(id, title, body, _notificationDetails()); } } - From 4532ecbae05019a5ad44e67f2c4cfdec1c344694 Mon Sep 17 00:00:00 2001 From: AhmedHanafy725 Date: Tue, 15 Apr 2025 16:32:14 +0200 Subject: [PATCH 27/46] Revert the changes in the wrong manifest file --- app/android/app/src/profile/AndroidManifest.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml index 22698de17..3f356e332 100644 --- a/app/android/app/src/profile/AndroidManifest.xml +++ b/app/android/app/src/profile/AndroidManifest.xml @@ -4,11 +4,4 @@ to allow setting breakpoints, to provide hot reload, etc. --> - - - - - - - From c4873e7693eab76c5a98b7cf9e93f7f1972c24a1 Mon Sep 17 00:00:00 2001 From: AhmedHanafy725 Date: Tue, 15 Apr 2025 16:32:49 +0200 Subject: [PATCH 28/46] Update the gradle repos --- app/android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/android/build.gradle b/app/android/build.gradle index 9b2b46c3d..691f48feb 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -5,8 +5,8 @@ allprojects { mavenCentral() maven { url 'https://jitpack.io' } maven { url 'https://maven.google.com' } - maven { url "https://plugins.gradle.org/m2/" } - maven {url 'https://maven.transistorsoft.com/repo'} + // [required] background_fetch + maven { url "${project(':background_fetch').projectDir}/libs" } } } From d28d9b2756b1da5a291c7029392e98e57b16be22 Mon Sep 17 00:00:00 2001 From: AhmedHanafy725 Date: Tue, 15 Apr 2025 16:34:57 +0200 Subject: [PATCH 29/46] Update build gradle dependencies --- app/android/app/build_local | 4 ++++ app/android/app/build_production | 4 ++++ app/android/app/build_staging | 4 ++++ app/android/app/build_testing | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/app/android/app/build_local b/app/android/app/build_local index 841ab2c08..f3c3954af 100644 --- a/app/android/app/build_local +++ b/app/android/app/build_local @@ -40,6 +40,7 @@ android { } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } @@ -101,4 +102,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.activity:activity:1.9.3" + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' } diff --git a/app/android/app/build_production b/app/android/app/build_production index 841ab2c08..f3c3954af 100644 --- a/app/android/app/build_production +++ b/app/android/app/build_production @@ -40,6 +40,7 @@ android { } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } @@ -101,4 +102,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.activity:activity:1.9.3" + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' } diff --git a/app/android/app/build_staging b/app/android/app/build_staging index 841ab2c08..f3c3954af 100644 --- a/app/android/app/build_staging +++ b/app/android/app/build_staging @@ -40,6 +40,7 @@ android { } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } @@ -101,4 +102,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.activity:activity:1.9.3" + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' } diff --git a/app/android/app/build_testing b/app/android/app/build_testing index 841ab2c08..f3c3954af 100644 --- a/app/android/app/build_testing +++ b/app/android/app/build_testing @@ -40,6 +40,7 @@ android { } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } @@ -101,4 +102,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.activity:activity:1.9.3" + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' } From d9453e7e18ac09117751a8e0f9dcbadefa32a899 Mon Sep 17 00:00:00 2001 From: AhmedHanafy725 Date: Tue, 15 Apr 2025 16:41:33 +0200 Subject: [PATCH 30/46] Update the manifest permissions --- .../app/src/main/AndroidManifest_local | 32 ++++++++++++++++++- .../app/src/main/AndroidManifest_production | 32 ++++++++++++++++++- .../app/src/main/AndroidManifest_staging | 32 ++++++++++++++++++- .../app/src/main/AndroidManifest_testing | 32 ++++++++++++++++++- 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/app/android/app/src/main/AndroidManifest_local b/app/android/app/src/main/AndroidManifest_local index 3f0180a79..e4cfe7f80 100644 --- a/app/android/app/src/main/AndroidManifest_local +++ b/app/android/app/src/main/AndroidManifest_local @@ -1,6 +1,12 @@ - @@ -32,6 +38,22 @@ + + + + + + + + + + + + @@ -41,6 +63,14 @@ + + + + + + + + diff --git a/app/android/app/src/main/AndroidManifest_production b/app/android/app/src/main/AndroidManifest_production index 578607cbe..008252e71 100644 --- a/app/android/app/src/main/AndroidManifest_production +++ b/app/android/app/src/main/AndroidManifest_production @@ -1,6 +1,12 @@ - @@ -32,6 +38,22 @@ + + + + + + + + + + + + @@ -41,6 +63,14 @@ + + + + + + + + diff --git a/app/android/app/src/main/AndroidManifest_staging b/app/android/app/src/main/AndroidManifest_staging index e0b354202..afff88164 100644 --- a/app/android/app/src/main/AndroidManifest_staging +++ b/app/android/app/src/main/AndroidManifest_staging @@ -1,6 +1,12 @@ - @@ -32,6 +38,22 @@ + + + + + + + + + + + + @@ -41,6 +63,14 @@ + + + + + + + + diff --git a/app/android/app/src/main/AndroidManifest_testing b/app/android/app/src/main/AndroidManifest_testing index 2c829c582..715c55595 100644 --- a/app/android/app/src/main/AndroidManifest_testing +++ b/app/android/app/src/main/AndroidManifest_testing @@ -1,6 +1,12 @@ - @@ -32,6 +38,22 @@ + + + + + + + + + + + + @@ -41,6 +63,14 @@ + + + + + + + + From eaf9eab98bf54d78a69a44e84c2719f6caa11695 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Tue, 22 Apr 2025 15:25:41 +0200 Subject: [PATCH 31/46] only show notifications for nodes down last 2 days && group notifications --- app/lib/models/farm.dart | 2 + app/lib/services/background_service.dart | 36 ++++++++++++--- app/lib/services/nodes_check_service.dart | 1 + app/lib/services/notification_service.dart | 51 ++++++++++++++-------- 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/app/lib/models/farm.dart b/app/lib/models/farm.dart index 1bcecd923..e595b561a 100644 --- a/app/lib/models/farm.dart +++ b/app/lib/models/farm.dart @@ -4,9 +4,11 @@ class Node { Node({ required this.nodeId, required this.status, + this.updatedAt, }); final int nodeId; final NodeStatus status; + final int? updatedAt; } class Farm { diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index 40f7c0abf..770d0feb7 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:background_fetch/background_fetch.dart'; import 'package:threebotlogin/services/nodes_check_service.dart'; import 'notification_service.dart'; @@ -19,11 +21,33 @@ Future checkNodeStatus() async { final offlineNodes = await NodeCheckService.pingNodesInBackground(); if (offlineNodes.isNotEmpty) { - final nodeIds = offlineNodes.map((n) => n.nodeId).join(', '); - await NotificationService().showNotification( - title: 'Node Alert 🚨', - body: 'Offline node(s): $nodeIds', - ); + final twoDaysAgoTimestamp = + DateTime.now().subtract(const Duration(days: 2)).millisecondsSinceEpoch; + + final recentOfflineNodes = offlineNodes + .where((node) => node.updatedAt! > twoDaysAgoTimestamp) + .toList(); + + if (offlineNodes.isEmpty) return; + + const groupKey = 'offline_nodes'; + for (var node in recentOfflineNodes) { + await NotificationService().showNotification( + id: node.hashCode, + title: 'Node Alert 🚨', + body: 'Node ${node.nodeId} is offline', + groupKey: groupKey, + ); + } + + if (recentOfflineNodes.length > 1) { + await NotificationService().showNotification( + id: 0, + title: 'Multiple Nodes Offline', + body: '${offlineNodes.length} nodes are currently offline', + groupKey: groupKey, + isGroupSummary: true, + ); + } } } - diff --git a/app/lib/services/nodes_check_service.dart b/app/lib/services/nodes_check_service.dart index c855dee38..ca618d92a 100644 --- a/app/lib/services/nodes_check_service.dart +++ b/app/lib/services/nodes_check_service.dart @@ -31,6 +31,7 @@ class NodeCheckService { final nodes = nodesData .map((node) => Node( nodeId: node.nodeId, + updatedAt: node.updatedAt, status: NodeStatus.values.firstWhere( (e) => e.toString().toLowerCase() == diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index fa94d88d4..aef2ef004 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -33,28 +33,43 @@ class NotificationService { _isInitialized = true; } - NotificationDetails _notificationDetails() { - return const NotificationDetails( - android: AndroidNotificationDetails( - 'node_status_channel', - 'Node Status', - channelDescription: 'Notify user when node goes offline', - importance: Importance.max, - priority: Priority.high, - ), - iOS: DarwinNotificationDetails( - presentAlert: true, - presentSound: true, - presentBadge: true, - ), - ); - } - Future showNotification({ int id = 0, required String title, required String body, + String? groupKey, + bool isGroupSummary = false, }) async { - await notificationsPlugin.show(id, title, body, _notificationDetails()); + final androidDetails = groupKey != null + ? AndroidNotificationDetails( + 'node_status_channel', + 'Node Status', + channelDescription: 'Notify user when node goes offline', + importance: Importance.max, + priority: Priority.high, + groupKey: groupKey, + setAsGroupSummary: isGroupSummary, + ) + : const AndroidNotificationDetails( + 'node_status_channel', + 'Node Status', + channelDescription: 'Notify user when node goes offline', + importance: Importance.max, + priority: Priority.high, + ); + + final iosDetails = DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + threadIdentifier: groupKey, + ); + + final notificationDetails = NotificationDetails( + android: androidDetails, + iOS: iosDetails, + ); + + await notificationsPlugin.show(id, title, body, notificationDetails); } } From 78a059ba2fea8df201c5249e717828ced90716d3 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Tue, 22 Apr 2025 15:32:12 +0200 Subject: [PATCH 32/46] remove unused import --- app/lib/services/background_service.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index 770d0feb7..5aad4ad7d 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:background_fetch/background_fetch.dart'; import 'package:threebotlogin/services/nodes_check_service.dart'; import 'notification_service.dart'; From 7bcfb884de54270abc59aa6696f30f2132ecc743 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Tue, 22 Apr 2025 15:51:22 +0200 Subject: [PATCH 33/46] update checking nodes for last 7 days --- app/lib/services/background_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index 5aad4ad7d..17ea26c91 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -20,7 +20,7 @@ Future checkNodeStatus() async { if (offlineNodes.isNotEmpty) { final twoDaysAgoTimestamp = - DateTime.now().subtract(const Duration(days: 2)).millisecondsSinceEpoch; + DateTime.now().subtract(const Duration(days: 7)).millisecondsSinceEpoch; final recentOfflineNodes = offlineNodes .where((node) => node.updatedAt! > twoDaysAgoTimestamp) From 01c6b323ea0c071f7f1a6bff65786fb9aff4b804 Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Thu, 24 Apr 2025 09:48:02 +0200 Subject: [PATCH 34/46] Update app/lib/services/background_service.dart Co-authored-by: AhmedHanafy725 <41957921+AhmedHanafy725@users.noreply.github.com> --- app/lib/services/background_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index 17ea26c91..fdab01d18 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -19,7 +19,7 @@ Future checkNodeStatus() async { final offlineNodes = await NodeCheckService.pingNodesInBackground(); if (offlineNodes.isNotEmpty) { - final twoDaysAgoTimestamp = + final sevenDaysAgoTimestamp = DateTime.now().subtract(const Duration(days: 7)).millisecondsSinceEpoch; final recentOfflineNodes = offlineNodes From ff260225563f40e8e4762c2fd8e2498b790b7513 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Thu, 24 Apr 2025 11:28:04 +0200 Subject: [PATCH 35/46] apply pr comments && apply the exponential notification --- .github/workflows/build.yml | 6 -- app/lib/services/background_service.dart | 88 ++++++++++++++++-------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9ab41d3b..6013718bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,12 +24,6 @@ jobs: flutter-version: "3.27.2" channel: "stable" - - name: Clean Gradle & Flutter build cache - run: | - rm -rf ~/.gradle - rm -rf ~/.pub-cache/hosted/pub.dev/background_fetch* - flutter clean - - name: Get dependencies run: flutter pub get diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index fdab01d18..b90049a39 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -17,35 +17,65 @@ void backgroundFetchHeadlessTask(HeadlessTask task) async { Future checkNodeStatus() async { final offlineNodes = await NodeCheckService.pingNodesInBackground(); + if (offlineNodes.isEmpty) return; - if (offlineNodes.isNotEmpty) { - final sevenDaysAgoTimestamp = - DateTime.now().subtract(const Duration(days: 7)).millisecondsSinceEpoch; - - final recentOfflineNodes = offlineNodes - .where((node) => node.updatedAt! > twoDaysAgoTimestamp) - .toList(); - - if (offlineNodes.isEmpty) return; - - const groupKey = 'offline_nodes'; - for (var node in recentOfflineNodes) { - await NotificationService().showNotification( - id: node.hashCode, - title: 'Node Alert 🚨', - body: 'Node ${node.nodeId} is offline', - groupKey: groupKey, - ); - } - - if (recentOfflineNodes.length > 1) { - await NotificationService().showNotification( - id: 0, - title: 'Multiple Nodes Offline', - body: '${offlineNodes.length} nodes are currently offline', - groupKey: groupKey, - isGroupSummary: true, - ); - } + final now = DateTime.now().millisecondsSinceEpoch; + final sevenDaysAgoTimestamp = + DateTime.now().subtract(const Duration(days: 7)).millisecondsSinceEpoch; + + final nodesToNotify = offlineNodes.where((node) { + if (node.updatedAt! <= sevenDaysAgoTimestamp) return false; + + final downtime = Duration(milliseconds: now - node.updatedAt!); + final checkInterval = _getCheckInterval(downtime); + + return downtime.inMinutes % checkInterval.inMinutes < 15; + }).toList(); + + if (nodesToNotify.isEmpty) return; + + const groupKey = 'offline_nodes'; + if (nodesToNotify.length == 1) { + final node = nodesToNotify.first; + final downtime = + _formatDowntime(Duration(milliseconds: now - node.updatedAt!)); + + await NotificationService().showNotification( + id: node.hashCode, + title: 'Node Alert 🚨', + body: 'Node ${node.nodeId} has been offline for $downtime', + groupKey: groupKey, + ); + } else { + await NotificationService().showNotification( + id: nodesToNotify.hashCode, + title: 'Multiple Nodes Offline', + body: '${nodesToNotify.length} nodes are currently offline', + groupKey: groupKey, + ); + } +} + +Duration _getCheckInterval(Duration downtime) { + if (downtime < const Duration(hours: 1)) { + return const Duration(minutes: 15); // 0-1 hour: check every 15 min + } else if (downtime < const Duration(hours: 4)) { + return const Duration(hours: 1); // 1-4 hours: check every hour + } else if (downtime < const Duration(hours: 24)) { + return const Duration(hours: 4); // 4-24 hours: check every 4 hours + } else if (downtime < const Duration(days: 3)) { + return const Duration(hours: 12); // 1-3 days: check every 12 hours + } else { + return const Duration(days: 1); // 3-7 days: check once per day + } +} + +String _formatDowntime(Duration duration) { + if (duration.inDays > 0) { + return '${duration.inDays} days'; + } else if (duration.inHours > 0) { + return '${duration.inHours} hours'; + } else { + return '${duration.inMinutes} minutes'; } } From 56afeb9a84bae75db3b7448050f2c9a19aee2329 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Thu, 24 Apr 2025 12:30:38 +0200 Subject: [PATCH 36/46] handle notification grouping --- app/lib/services/background_service.dart | 28 ++++++++++------------ app/lib/services/notification_service.dart | 26 +++++++------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index b90049a39..bb8fca3ba 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -35,25 +35,23 @@ Future checkNodeStatus() async { if (nodesToNotify.isEmpty) return; const groupKey = 'offline_nodes'; - if (nodesToNotify.length == 1) { - final node = nodesToNotify.first; + final StringBuffer bodyBuffer = StringBuffer(); + + for (final node in nodesToNotify) { final downtime = _formatDowntime(Duration(milliseconds: now - node.updatedAt!)); - await NotificationService().showNotification( - id: node.hashCode, - title: 'Node Alert 🚨', - body: 'Node ${node.nodeId} has been offline for $downtime', - groupKey: groupKey, - ); - } else { - await NotificationService().showNotification( - id: nodesToNotify.hashCode, - title: 'Multiple Nodes Offline', - body: '${nodesToNotify.length} nodes are currently offline', - groupKey: groupKey, - ); + bodyBuffer.writeln('Node ${node.nodeId}: offline for $downtime'); } + + await NotificationService().showNotification( + id: nodesToNotify.hashCode, + title: nodesToNotify.length == 1 + ? 'Node Alert 🚨' + : '${nodesToNotify.length} Nodes Offline 🚨', + body: bodyBuffer.toString().trim(), + groupKey: groupKey, + ); } Duration _getCheckInterval(Duration downtime) { diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index aef2ef004..e6fa4bbc1 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -40,29 +40,21 @@ class NotificationService { String? groupKey, bool isGroupSummary = false, }) async { - final androidDetails = groupKey != null - ? AndroidNotificationDetails( - 'node_status_channel', - 'Node Status', - channelDescription: 'Notify user when node goes offline', - importance: Importance.max, - priority: Priority.high, - groupKey: groupKey, - setAsGroupSummary: isGroupSummary, - ) - : const AndroidNotificationDetails( - 'node_status_channel', - 'Node Status', - channelDescription: 'Notify user when node goes offline', - importance: Importance.max, - priority: Priority.high, - ); + final androidDetails = AndroidNotificationDetails( + 'node_status_channel', + 'Node Status', + channelDescription: 'Notify user when node goes offline', + importance: Importance.max, + priority: Priority.high, + groupKey: groupKey, + ); final iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, threadIdentifier: groupKey, + interruptionLevel: InterruptionLevel.timeSensitive ); final notificationDetails = NotificationDetails( From 69ba0e6957e1d0d499b35d54f6f4cb93b9e57238 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Thu, 24 Apr 2025 13:16:04 +0200 Subject: [PATCH 37/46] handled tapping on notification --- app/lib/main.dart | 3 +- app/lib/services/notification_service.dart | 113 +++++++++++++++++---- 2 files changed, 94 insertions(+), 22 deletions(-) diff --git a/app/lib/main.dart b/app/lib/main.dart index bb1ea6eb3..fcae62a8c 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -32,7 +32,7 @@ extension ColorSchemeExtension on ColorScheme { ? const Color.fromARGB(255, 240, 240, 240) : const Color.fromARGB(255, 10, 10, 10); } - +final GlobalKey navigatorKey = GlobalKey(); Future main() async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); @@ -113,6 +113,7 @@ class MyApp extends ConsumerWidget { ); return MaterialApp( + navigatorKey: navigatorKey, theme: ThemeData().copyWith( colorScheme: kColorScheme, brightness: Brightness.light, diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index e6fa4bbc1..2157d6262 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -1,4 +1,9 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/main.dart'; +import 'package:threebotlogin/widgets/custom_dialog.dart'; class NotificationService { static final NotificationService _instance = NotificationService._internal(); @@ -11,6 +16,13 @@ class NotificationService { Future initNotification() async { if (_isInitialized) return; + final NotificationAppLaunchDetails? launchDetails = + await notificationsPlugin.getNotificationAppLaunchDetails(); + + if (launchDetails?.didNotificationLaunchApp ?? false) { + _handleNotificationTap(launchDetails?.notificationResponse); + } + const initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); const initSettingsIOS = DarwinInitializationSettings( @@ -24,7 +36,11 @@ class NotificationService { iOS: initSettingsIOS, ); - await notificationsPlugin.initialize(initSettings); + await notificationsPlugin.initialize( + initSettings, + onDidReceiveNotificationResponse: (details) => + _handleNotificationTap(details), + ); await notificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() @@ -40,28 +56,83 @@ class NotificationService { String? groupKey, bool isGroupSummary = false, }) async { - final androidDetails = AndroidNotificationDetails( - 'node_status_channel', - 'Node Status', - channelDescription: 'Notify user when node goes offline', - importance: Importance.max, - priority: Priority.high, - groupKey: groupKey, - ); + try { + if (!_isInitialized) { + await initNotification(); + } - final iosDetails = DarwinNotificationDetails( - presentAlert: true, - presentBadge: true, - presentSound: true, - threadIdentifier: groupKey, - interruptionLevel: InterruptionLevel.timeSensitive - ); + final androidDetails = AndroidNotificationDetails( + 'node_status_channel', + 'Node Status', + channelDescription: 'Notify user when node goes offline', + importance: Importance.max, + priority: Priority.high, + groupKey: groupKey, + ); - final notificationDetails = NotificationDetails( - android: androidDetails, - iOS: iosDetails, - ); + final iosDetails = DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + threadIdentifier: groupKey, + interruptionLevel: InterruptionLevel.timeSensitive); + + final notificationDetails = NotificationDetails( + android: androidDetails, + iOS: iosDetails, + ); + + final payload = json.encode({ + 'title': title, + 'body': body, + }); + + await notificationsPlugin.show( + id, + title, + body, + notificationDetails, + payload: payload, + ); + } catch (e) { + logger.e('[NotificationService] Failed to show notification: $e'); + } + } + + void _handleNotificationTap(NotificationResponse? response) { + if (response?.payload != null) { + final Map payload = json.decode(response!.payload!); + showNodeStatusDialog( + navigatorKey.currentContext!, + payload['title'], + payload['body'], + ); + } + } + + void showNodeStatusDialog(BuildContext context, String title, String body) { + try { + if (!context.mounted) return; - await notificationsPlugin.show(id, title, body, notificationDetails); + showDialog( + context: context, + builder: (BuildContext context) => CustomDialog( + type: DialogType.Warning, + image: Icons.warning, + title: title, + description: body, + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ); + } catch (e) { + logger.e('[NotificationService] Failed to show dialog: $e'); + } } } From 3aeeeeb2f9800a4748fcfb5dc232d3deefee6e40 Mon Sep 17 00:00:00 2001 From: Zainab Elgohary <40770501+zaelgohary@users.noreply.github.com> Date: Wed, 16 Apr 2025 16:45:15 +0200 Subject: [PATCH 38/46] Hide show phrase (#995) --- .../screens/identity_verification_screen.dart | 119 ++++++++++++++---- .../screens/mobile_registration_screen.dart | 12 +- .../services/shared_preference_service.dart | 2 - 3 files changed, 99 insertions(+), 34 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index 692a1c171..c676c02b6 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -40,6 +40,7 @@ class IdentityVerificationScreenState bool emailVerified = false; bool phoneVerified = false; bool isLoading = false; + bool failed = false; bool hidePhoneVerifyButton = false; bool emailInputValidated = false; int emailCountdown = 60; @@ -196,20 +197,44 @@ class IdentityVerificationScreenState } void getUserValues() async { - doubleName = (await getDoubleName())?.replaceAll('.3bot', '') ?? 'Unknown'; - phrase = (await getPhrase())!; - final emailMap = await getEmail(); - if (emailMap['email'] != null) { - email = emailMap['email']!; - changeEmailController.text = email; - emailVerified = (emailMap['sei'] != null); - } - final phoneMap = await getPhone(); - if (phoneMap['phone'] != null) { - phone = phoneMap['phone']!; - phoneVerified = (phoneMap['spi'] != null); + setState(() => isLoading = true); + try { + doubleName = + (await getDoubleName())?.replaceAll('.3bot', '') ?? 'Unknown'; + phrase = (await getPhrase()) ?? ''; + final emailMap = await getEmail(); + if (emailMap['email'] != null) { + email = emailMap['email']!; + changeEmailController.text = email; + emailVerified = (emailMap['sei'] != null); + } + final phoneMap = await getPhone(); + if (phoneMap['phone'] != null) { + phone = phoneMap['phone']!; + phoneVerified = (phoneMap['spi'] != null); + } + } catch (e) { + setState(() { + failed = true; + }); + logger.e('Failed to get user values due to $e'); + if (context.mounted) { + final loadingFarmsFailure = SnackBar( + content: Text( + 'Failed to get user values', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).colorScheme.errorContainer), + ), + duration: const Duration(seconds: 3), + ); + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(loadingFarmsFailure); + } + } finally { + if (mounted) setState(() => isLoading = false); } - setState(() {}); } Future copySeedPhrase() async { @@ -619,9 +644,44 @@ class IdentityVerificationScreenState @override Widget build(BuildContext context) { - return LayoutDrawer( - titleText: 'Identity', - content: Padding( + final Widget content; + if (isLoading) { + content = Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 15), + Text( + 'Loading identity information...', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold), + ), + ], + )); + } else if (failed) { + content = Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 15), + ElevatedButton.icon( + icon: const Icon(Icons.refresh), + label: const Text('Try Again'), + onPressed: () { + setState(() { + failed = false; + isLoading = true; + }); + getUserValues(); + }, + ), + ], + ), + ); + } else { + content = Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: SingleChildScrollView( child: Column( @@ -641,16 +701,18 @@ class IdentityVerificationScreenState title: Text(doubleName), ), customDivider(context: context), - Padding( - padding: const EdgeInsets.only(right: 2.0), - child: ListTile( - trailing: const Icon(Icons.visibility), - leading: const Icon(Icons.vpn_key), - title: const Text('Show phrase'), - onTap: _showPhrase, + if (phrase.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.only(right: 2.0), + child: ListTile( + trailing: const Icon(Icons.visibility), + leading: const Icon(Icons.vpn_key), + title: const Text('Show phrase'), + onTap: _showPhrase, + ), ), - ), - customDivider(context: context), + customDivider(context: context), + ], infoWidget(1, email, Icons.email, emailVerified), customDivider(context: context), infoWidget(2, phone, Icons.phone, phoneVerified), @@ -661,7 +723,12 @@ class IdentityVerificationScreenState ], ), ), - ), + ); + } + + return LayoutDrawer( + titleText: 'Identity', + content: content, ); } } diff --git a/app/lib/screens/mobile_registration_screen.dart b/app/lib/screens/mobile_registration_screen.dart index e42feef5f..7e0716b1e 100644 --- a/app/lib/screens/mobile_registration_screen.dart +++ b/app/lib/screens/mobile_registration_screen.dart @@ -254,12 +254,12 @@ class _MobileRegistrationScreenState extends State { } void saveRegistration() async { - savePrivateKey(_registrationData.keyPair.secretKey.extractBytes()); - savePublicKey(_registrationData.keyPair.publicKey); - saveFingerprint(false); - saveEmail(_registrationData.email, null); - saveDoubleName(_registrationData.doubleName); - savePhrase(_registrationData.phrase); + await savePrivateKey(_registrationData.keyPair.secretKey.extractBytes()); + await savePublicKey(_registrationData.keyPair.publicKey); + await saveFingerprint(false); + await saveEmail(_registrationData.email, null); + await saveDoubleName(_registrationData.doubleName); + await savePhrase(_registrationData.phrase); FlutterPkid client = await getPkidClient(); client.setPKidDoc('email', json.encode({'email': _registrationData.email})); diff --git a/app/lib/services/shared_preference_service.dart b/app/lib/services/shared_preference_service.dart index 3be150c24..6738b4d63 100644 --- a/app/lib/services/shared_preference_service.dart +++ b/app/lib/services/shared_preference_service.dart @@ -111,8 +111,6 @@ Future> getEdCurveKeys() async { Future savePhrase(String phrase) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.remove('phrase'); - prefs.setString('phrase', phrase); } From 48b9cd1682e65329ac27ae63da40d43f11aab959 Mon Sep 17 00:00:00 2001 From: Alaa228 Date: Thu, 24 Apr 2025 09:46:59 +0200 Subject: [PATCH 39/46] Handle duplicated contacts in favorite List (#983) * handle duplicated contacts * undo latest solution * fix adding duplicated wallet in fav list * Fixed the duplicated contacts issue * update condition when editing address --- app/lib/screens/wallets/contacts.dart | 10 ++++--- app/lib/widgets/wallets/add_edit_contact.dart | 29 ++++++++++++------- app/lib/widgets/wallets/contacts_widget.dart | 9 ++++-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/app/lib/screens/wallets/contacts.dart b/app/lib/screens/wallets/contacts.dart index e434eb204..f98ff4659 100644 --- a/app/lib/screens/wallets/contacts.dart +++ b/app/lib/screens/wallets/contacts.dart @@ -39,8 +39,6 @@ class _ContactsScreenState extends State { _loadFavouriteContacts() async { myPkidContacts = await getPkidContacts(); - myPkidContacts = - myPkidContacts.where((c) => c.type == widget.chainType).toList(); setState(() {}); } @@ -60,6 +58,7 @@ class _ContactsScreenState extends State { onAddContact: _onAddContact, chainType: widget.chainType, contacts: [...myPkidContacts, ...myWalletContacts], + getAllContacts: () => [...myPkidContacts, ...myWalletContacts], )); } @@ -141,15 +140,18 @@ class _ContactsScreenState extends State { children: [ ContactsWidget( contacts: myWalletContacts - .where( - (c) => c.address != widget.currentWalletAddress && c.type == widget.chainType) + .where((c) => + c.address != widget.currentWalletAddress && + c.type == widget.chainType) .toList(), + chainType: widget.chainType, onSelectToAddress: widget.onSelectToAddress), ContactsWidget( contacts: myPkidContacts, onSelectToAddress: widget.onSelectToAddress, onDeleteContact: _onDeleteContact, onEditContact: _openEditContactOverlay, + chainType: widget.chainType, canEditAndDelete: true, ), ], diff --git a/app/lib/widgets/wallets/add_edit_contact.dart b/app/lib/widgets/wallets/add_edit_contact.dart index 6af367a6a..32f6bbc61 100644 --- a/app/lib/widgets/wallets/add_edit_contact.dart +++ b/app/lib/widgets/wallets/add_edit_contact.dart @@ -17,6 +17,7 @@ class AddEditContact extends StatefulWidget { this.name = '', this.address = '', this.onEditContact, + this.getAllContacts, }); final void Function(PkidContact addedContact)? onAddContact; @@ -27,6 +28,7 @@ class AddEditContact extends StatefulWidget { final String address; final void Function(String oldName, String newName, String newAddress)? onEditContact; + final List Function()? getAllContacts; @override State createState() { @@ -81,31 +83,34 @@ class _AddEditContactState extends State { bool _validateAddress(String contactAddress, {bool edit = false}) { addressError = null; + final allContacts = widget.getAllContacts != null + ? widget.getAllContacts!() + : widget.contacts; if (contactAddress.isEmpty) { addressError = "Address can't be empty"; return false; } - final contacts = widget.contacts.where((c) => c.address == contactAddress); - if (edit && contactAddress != widget.address && contacts.isNotEmpty) { - addressError = 'Address is used in another contact'; - return false; - } else if (!edit && contacts.isNotEmpty) { - addressError = - 'Address exists in another ${contacts.first.type.name} contact'; + final isDuplicate = allContacts.any((c) => c.address == contactAddress && (!edit || (edit && widget.address != contactAddress))); + + if (isDuplicate) { + addressError = 'Address is used in another contact'; return false; } + if (_selectedChainType == ChainType.TFChain && contactAddress.length != 48) { addressError = 'Address length should be 48 characters'; return false; } + if (_selectedChainType == ChainType.Stellar && !isValidStellarAddress(contactAddress)) { addressError = 'Invaild Stellar address'; return false; } + return true; } @@ -126,10 +131,12 @@ class _AddEditContactState extends State { Icons.error, DialogType.Error); return; } - if (chainType == widget.chainType) { - widget.onAddContact!(PkidContact( - name: contactName, address: contactAddress, type: chainType)); - } + widget.onAddContact?.call(PkidContact( + name: contactName, + address: contactAddress, + type: chainType, + )); + if (!context.mounted) return; Navigator.pop(context); } diff --git a/app/lib/widgets/wallets/contacts_widget.dart b/app/lib/widgets/wallets/contacts_widget.dart index 7c462290e..36b9696e1 100644 --- a/app/lib/widgets/wallets/contacts_widget.dart +++ b/app/lib/widgets/wallets/contacts_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:threebotlogin/models/contact.dart'; +import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/widgets/wallets/contact_card.dart'; class ContactsWidget extends StatelessWidget { @@ -9,19 +10,21 @@ class ContactsWidget extends StatelessWidget { required this.onSelectToAddress, this.onDeleteContact, this.onEditContact, + required this.chainType, this.canEditAndDelete = false, }); - final List contacts; final void Function(String address) onSelectToAddress; final bool canEditAndDelete; final void Function(String name)? onDeleteContact; final void Function(String oldName, String oldAddress)? onEditContact; + final ChainType chainType; @override Widget build(BuildContext context) { + final filteredContacts = contacts.where((c) => c.type == chainType).toList(); Widget content; - if (contacts.isEmpty) { + if (filteredContacts.isEmpty) { content = Center( child: Text( 'No contacts yet.', @@ -33,7 +36,7 @@ class ContactsWidget extends StatelessWidget { ); } else { content = ListView(children: [ - for (final contact in contacts) + for (final contact in filteredContacts) InkWell( onTap: () { onSelectToAddress(contact.address); From 7b09e6acc5f4437a733ff30a59728ca927683c06 Mon Sep 17 00:00:00 2001 From: Zainab Elgohary <40770501+zaelgohary@users.noreply.github.com> Date: Sun, 27 Apr 2025 10:04:27 +0300 Subject: [PATCH 40/46] Fix auth timeout routing (#1000) * Creating app lifecycle observer and use it in main.dart w navigator key, Replace Navigator routes w navigator key * Remove unused code * Revert navigator key changes from screens * Revert main screen changes --- app/lib/main.dart | 114 +++++++++++--------- app/lib/screens/app_lifecycle_observer.dart | 67 ++++++++++++ app/lib/screens/home_screen.dart | 56 +++------- 3 files changed, 143 insertions(+), 94 deletions(-) create mode 100644 app/lib/screens/app_lifecycle_observer.dart diff --git a/app/lib/main.dart b/app/lib/main.dart index fcae62a8c..2054121a2 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/screens/app_lifecycle_observer.dart'; import 'package:threebotlogin/screens/splash_screen.dart'; import 'package:threebotlogin/services/background_service.dart'; import 'package:threebotlogin/services/notification_service.dart'; @@ -32,7 +33,12 @@ extension ColorSchemeExtension on ColorScheme { ? const Color.fromARGB(255, 240, 240, 240) : const Color.fromARGB(255, 10, 10, 10); } + final GlobalKey navigatorKey = GlobalKey(); + +final lastPausedProvider = + StateProvider((ref) => DateTime.now().millisecondsSinceEpoch); + Future main() async { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); @@ -112,61 +118,65 @@ class MyApp extends ConsumerWidget { Theme.of(context).textTheme, ); - return MaterialApp( - navigatorKey: navigatorKey, - theme: ThemeData().copyWith( - colorScheme: kColorScheme, - brightness: Brightness.light, - textTheme: textTheme, - appBarTheme: const AppBarTheme().copyWith( - backgroundColor: kColorScheme.primary, - foregroundColor: kColorScheme.onPrimary, - ), - cardTheme: const CardTheme().copyWith( - color: kColorScheme.surfaceContainer, - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5)), - backgroundColor: kColorScheme.primaryContainer), - ), - expansionTileTheme: const ExpansionTileThemeData().copyWith( - backgroundColor: kColorScheme.backgroundDarker, - collapsedBackgroundColor: ThemeData().colorScheme.surface), - bottomNavigationBarTheme: const BottomNavigationBarThemeData().copyWith( - selectedItemColor: kColorScheme.primary, - unselectedItemColor: kColorScheme.secondary, - ), - ), - darkTheme: ThemeData( - useMaterial3: true, - colorScheme: kDarkColorScheme, - brightness: Brightness.dark, - textTheme: textTheme, - appBarTheme: const AppBarTheme().copyWith( - backgroundColor: kDarkColorScheme.primaryContainer, - foregroundColor: kDarkColorScheme.onPrimaryContainer, - ), - cardTheme: const CardTheme().copyWith( - color: kDarkColorScheme.surfaceContainer, - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5)), - backgroundColor: kDarkColorScheme.primaryContainer), + return AppLifecycleObserver( + child: MaterialApp( + navigatorKey: navigatorKey, + theme: ThemeData().copyWith( + colorScheme: kColorScheme, + brightness: Brightness.light, + textTheme: textTheme, + appBarTheme: const AppBarTheme().copyWith( + backgroundColor: kColorScheme.primary, + foregroundColor: kColorScheme.onPrimary, + ), + cardTheme: const CardTheme().copyWith( + color: kColorScheme.surfaceContainer, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5)), + backgroundColor: kColorScheme.primaryContainer), + ), + expansionTileTheme: const ExpansionTileThemeData().copyWith( + backgroundColor: kColorScheme.backgroundDarker, + collapsedBackgroundColor: ThemeData().colorScheme.surface), + bottomNavigationBarTheme: + const BottomNavigationBarThemeData().copyWith( + selectedItemColor: kColorScheme.primary, + unselectedItemColor: kColorScheme.secondary, + ), ), - expansionTileTheme: const ExpansionTileThemeData().copyWith( - backgroundColor: kDarkColorScheme.backgroundDarker, - collapsedBackgroundColor: kDarkColorScheme.surface), - bottomNavigationBarTheme: const BottomNavigationBarThemeData().copyWith( - selectedItemColor: kDarkColorScheme.primary, - unselectedItemColor: kDarkColorScheme.secondary, + darkTheme: ThemeData( + useMaterial3: true, + colorScheme: kDarkColorScheme, + brightness: Brightness.dark, + textTheme: textTheme, + appBarTheme: const AppBarTheme().copyWith( + backgroundColor: kDarkColorScheme.primaryContainer, + foregroundColor: kDarkColorScheme.onPrimaryContainer, + ), + cardTheme: const CardTheme().copyWith( + color: kDarkColorScheme.surfaceContainer, + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5)), + backgroundColor: kDarkColorScheme.primaryContainer), + ), + expansionTileTheme: const ExpansionTileThemeData().copyWith( + backgroundColor: kDarkColorScheme.backgroundDarker, + collapsedBackgroundColor: kDarkColorScheme.surface), + bottomNavigationBarTheme: + const BottomNavigationBarThemeData().copyWith( + selectedItemColor: kDarkColorScheme.primary, + unselectedItemColor: kDarkColorScheme.secondary, + ), ), + themeMode: themeMode, + home: SplashScreen(initDone: initDone, registered: registered), ), - themeMode: themeMode, - home: SplashScreen(initDone: initDone, registered: registered), ); } } diff --git a/app/lib/screens/app_lifecycle_observer.dart b/app/lib/screens/app_lifecycle_observer.dart new file mode 100644 index 000000000..efd05e08f --- /dev/null +++ b/app/lib/screens/app_lifecycle_observer.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/main.dart'; +import 'package:threebotlogin/screens/home_screen.dart'; + +class AppLifecycleObserver extends ConsumerStatefulWidget { + final Widget child; + const AppLifecycleObserver({super.key, required this.child}); + + @override + ConsumerState createState() => + _AppLifecycleObserverState(); +} + +class _AppLifecycleObserverState extends ConsumerState + with WidgetsBindingObserver { + final int pinCheckTimeout = 60000 * 5; // 5 minute + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + Future.microtask(() { + ref.read(lastPausedProvider.notifier).state = + DateTime.now().millisecondsSinceEpoch; + }); + logger.i('AppLifecycleObserver initialized.'); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + logger.i('AppLifecycleObserver disposed.'); + super.dispose(); + } + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + super.didChangeAppLifecycleState(state); + logger.i('AppLifecycleState changed: $state'); + + final now = DateTime.now().millisecondsSinceEpoch; + final lastPaused = ref.read(lastPausedProvider); + + if (state == AppLifecycleState.paused) { + ref.read(lastPausedProvider.notifier).state = now; + logger.i('App Paused. Last paused time updated: $now'); + } else if (state == AppLifecycleState.resumed) { + if (now - lastPaused >= pinCheckTimeout) { + logger.i('Timeout expired. Requiring authentication.'); + // Navigate to Home Screen + WidgetsBinding.instance.addPostFrameCallback((_) { + navigatorKey.currentState?.push( + MaterialPageRoute(builder: (context) => const HomeScreen()), + ); + }); + ref.read(lastPausedProvider.notifier).state = now; + } + } + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/app/lib/screens/home_screen.dart b/app/lib/screens/home_screen.dart index 78ffbe34f..b44a63933 100644 --- a/app/lib/screens/home_screen.dart +++ b/app/lib/screens/home_screen.dart @@ -16,6 +16,7 @@ import 'package:threebotlogin/events/go_wallet_event.dart'; import 'package:threebotlogin/events/new_login_event.dart'; import 'package:threebotlogin/events/uni_link_event.dart'; import 'package:threebotlogin/helpers/globals.dart'; +import 'package:threebotlogin/main.dart'; import 'package:threebotlogin/providers/wallets_provider.dart'; import 'package:threebotlogin/screens/authentication_screen.dart'; import 'package:threebotlogin/services/socket_service.dart'; @@ -25,26 +26,30 @@ import 'package:threebotlogin/widgets/email_verification_needed.dart'; import 'package:uni_links/uni_links.dart'; /* Screen shows tab bar and all pages defined in router.dart */ -class HomeScreen extends StatefulWidget { +class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key, this.initialLink, this.backendConnection}); final String? initialLink; final BackendConnection? backendConnection; @override - State createState() => _HomeScreenState(); + ConsumerState createState() => _HomeScreenState(); } -class _HomeScreenState extends State - with WidgetsBindingObserver, SingleTickerProviderStateMixin { +class _HomeScreenState extends ConsumerState + with SingleTickerProviderStateMixin { Globals globals = Globals(); StreamSubscription? _sub; String? initialLink; bool timeoutExpiredInBackground = true; bool pinCheckOpen = false; - int lastCheck = 0; - final int pinCheckTimeout = 60000 * 5; - _HomeScreenState(); + @override + void dispose() { + _sub?.cancel(); + globals.tabController.removeListener(_handleTabSelection); + globals.tabController.dispose(); + super.dispose(); + } void checkPinAndNavigateIfSuccess(int indexIfAuthIsSuccess) async { String? pin = await getPin(); @@ -63,7 +68,8 @@ class _HomeScreenState extends State pinCheckOpen = false; if (authenticated != null && authenticated) { - lastCheck = DateTime.now().millisecondsSinceEpoch; + ref.read(lastPausedProvider.notifier).state = + DateTime.now().millisecondsSinceEpoch; timeoutExpiredInBackground = false; globals.tabController.animateTo(indexIfAuthIsSuccess); } @@ -175,40 +181,6 @@ class _HomeScreenState extends State Events().onEvent(PhoneEvent().runtimeType, (PhoneEvent event) { phoneVerification(context); }); - - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - _sub?.cancel(); - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.resumed) { - if (pinCheckOpen) { - return; - } - - int timeSpendWithPausedApp = - DateTime.now().millisecondsSinceEpoch - lastCheck; - - if (timeSpendWithPausedApp >= pinCheckTimeout) { - timeoutExpiredInBackground = true; - } - - if (Globals().router.pinRequired(globals.tabController.index) && - timeoutExpiredInBackground) { - int homeTab = 0; - globals.tabController.animateTo(homeTab); - } - } else if (state == AppLifecycleState.inactive) { - } else if (state == AppLifecycleState.paused) { - lastCheck = DateTime.now().millisecondsSinceEpoch; - } } Future initUniLinks() async { From b877ea901dceac6cd0ce0be9471b501f1107bfb5 Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Sun, 13 Apr 2025 11:18:56 +0200 Subject: [PATCH 41/46] fix ios issue and show notification with offline nodes --- app/lib/main.dart | 1 + app/lib/screens/identity_verification_screen.dart | 1 + app/pubspec.lock | 2 +- app/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/lib/main.dart b/app/lib/main.dart index 2054121a2..9b6b1e941 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/screens/app_lifecycle_observer.dart'; +import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/screens/splash_screen.dart'; import 'package:threebotlogin/services/background_service.dart'; import 'package:threebotlogin/services/notification_service.dart'; diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index c676c02b6..7a550648b 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -9,6 +9,7 @@ import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/main.dart'; import 'package:threebotlogin/screens/authentication_screen.dart'; +import 'package:threebotlogin/services/notification_service.dart'; import 'package:threebotlogin/services/open_kyc_service.dart'; import 'package:threebotlogin/services/pkid_service.dart'; import 'package:threebotlogin/services/tools_service.dart'; diff --git a/app/pubspec.lock b/app/pubspec.lock index 8523ff421..3a9e61b92 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -69,7 +69,7 @@ packages: sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" url: "https://pub.dev" source: hosted - version: "0.1.25" + version: "0.1.21" background_fetch: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index ca6e04ef8..6e5b232cc 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -84,7 +84,7 @@ dependencies: infinite_scroll_pagination: ^4.1.0 intl_mobile_field: ^1.1.1 mobile_scanner: 5.2.3 - flutter_local_notifications: ^19.1.0 + flutter_local_notifications: ^19.0.0 background_fetch: ^1.3.8 dev_dependencies: flutter_test: From ffead77e6b7bc81b118a66381c3e66ad0dc11c0e Mon Sep 17 00:00:00 2001 From: AlaaElattar Date: Mon, 14 Apr 2025 11:59:38 +0200 Subject: [PATCH 42/46] update pubspec.lock --- app/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pubspec.lock b/app/pubspec.lock index 3a9e61b92..8523ff421 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -69,7 +69,7 @@ packages: sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" url: "https://pub.dev" source: hosted - version: "0.1.21" + version: "0.1.25" background_fetch: dependency: "direct main" description: From 4a4be607b2cfc0e26ca75f73c3bcc31342c9f88b Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Mon, 28 Apr 2025 16:08:25 +0300 Subject: [PATCH 43/46] fix workflow --- app/lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/lib/main.dart b/app/lib/main.dart index c98b1f79a..2054121a2 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -5,8 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/screens/app_lifecycle_observer.dart'; -import 'package:threebotlogin/helpers/logger.dart'; -import 'package:threebotlogin/screens/app_lifecycle_observer.dart'; import 'package:threebotlogin/screens/splash_screen.dart'; import 'package:threebotlogin/services/background_service.dart'; import 'package:threebotlogin/services/notification_service.dart'; From a0016f7732271e019c49a8ffe1070c4a7f8c326a Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Mon, 28 Apr 2025 16:49:04 +0300 Subject: [PATCH 44/46] fix equation && workflow --- app/lib/screens/identity_verification_screen.dart | 1 - app/lib/services/background_service.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/lib/screens/identity_verification_screen.dart b/app/lib/screens/identity_verification_screen.dart index 7a550648b..c676c02b6 100644 --- a/app/lib/screens/identity_verification_screen.dart +++ b/app/lib/screens/identity_verification_screen.dart @@ -9,7 +9,6 @@ import 'package:threebotlogin/helpers/globals.dart'; import 'package:threebotlogin/helpers/logger.dart'; import 'package:threebotlogin/main.dart'; import 'package:threebotlogin/screens/authentication_screen.dart'; -import 'package:threebotlogin/services/notification_service.dart'; import 'package:threebotlogin/services/open_kyc_service.dart'; import 'package:threebotlogin/services/pkid_service.dart'; import 'package:threebotlogin/services/tools_service.dart'; diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index bb8fca3ba..53b22fbd2 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -29,7 +29,7 @@ Future checkNodeStatus() async { final downtime = Duration(milliseconds: now - node.updatedAt!); final checkInterval = _getCheckInterval(downtime); - return downtime.inMinutes % checkInterval.inMinutes < 15; + return downtime.inMinutes % checkInterval.inMinutes == 0; }).toList(); if (nodesToNotify.isEmpty) return; From d7a1b8017658791a4e3a0d61dd2cec736f68d457 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Wed, 30 Apr 2025 12:17:17 +0300 Subject: [PATCH 45/46] undo the equation --- app/lib/services/background_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index 53b22fbd2..bb8fca3ba 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -29,7 +29,7 @@ Future checkNodeStatus() async { final downtime = Duration(milliseconds: now - node.updatedAt!); final checkInterval = _getCheckInterval(downtime); - return downtime.inMinutes % checkInterval.inMinutes == 0; + return downtime.inMinutes % checkInterval.inMinutes < 15; }).toList(); if (nodesToNotify.isEmpty) return; From c2b8c0b22e7931e9e2637bea99e1ae960b1da1a9 Mon Sep 17 00:00:00 2001 From: Alaa Elattar Date: Wed, 30 Apr 2025 16:12:50 +0300 Subject: [PATCH 46/46] fixed time conversion --- app/lib/services/background_service.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/lib/services/background_service.dart b/app/lib/services/background_service.dart index bb8fca3ba..45a7af78a 100644 --- a/app/lib/services/background_service.dart +++ b/app/lib/services/background_service.dart @@ -24,9 +24,10 @@ Future checkNodeStatus() async { DateTime.now().subtract(const Duration(days: 7)).millisecondsSinceEpoch; final nodesToNotify = offlineNodes.where((node) { - if (node.updatedAt! <= sevenDaysAgoTimestamp) return false; + final nodeUpdatedAtMs = node.updatedAt! * 1000; + if (nodeUpdatedAtMs <= sevenDaysAgoTimestamp) return false; - final downtime = Duration(milliseconds: now - node.updatedAt!); + final downtime = Duration(milliseconds: now - nodeUpdatedAtMs); final checkInterval = _getCheckInterval(downtime); return downtime.inMinutes % checkInterval.inMinutes < 15; @@ -38,8 +39,9 @@ Future checkNodeStatus() async { final StringBuffer bodyBuffer = StringBuffer(); for (final node in nodesToNotify) { + final nodeUpdatedAtMs = node.updatedAt! * 1000; final downtime = - _formatDowntime(Duration(milliseconds: now - node.updatedAt!)); + _formatDowntime(Duration(milliseconds: now - nodeUpdatedAtMs)); bodyBuffer.writeln('Node ${node.nodeId}: offline for $downtime'); }