From 30d32df1c09e1404e5057ad7b8442ba67ba37696 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Mon, 18 May 2026 14:22:09 +0300 Subject: [PATCH] Sync update: 2026-05-18 14:22:09 --- whatsapp_app/lib/config/app_config.dart | 4 +- .../lib/controllers/chat_controller.dart | 5 + whatsapp_app/lib/main.dart | 16 ++- .../lib/services/firebase_service.dart | 120 +++++++++++++++++ .../lib/services/whatsapp_service.dart | 1 + whatsapp_app/pubspec.lock | 124 +++++++++++++++++- whatsapp_app/pubspec.yaml | 3 + whatsapp_bridge/server.js | 1 + 8 files changed, 264 insertions(+), 10 deletions(-) create mode 100644 whatsapp_app/lib/services/firebase_service.dart diff --git a/whatsapp_app/lib/config/app_config.dart b/whatsapp_app/lib/config/app_config.dart index fa5aab1..baec0c3 100644 --- a/whatsapp_app/lib/config/app_config.dart +++ b/whatsapp_app/lib/config/app_config.dart @@ -1,8 +1,8 @@ class AppConfig { - static const String serverHost = "mywhatsappapp.interlap.com"; + static const String serverHost = "mywhatsapp.intaleqapp.com"; static const int serverPort = 3025; static const String wsUrl = "ws://$serverHost:$serverPort"; - + static const int maxReconnectAttempts = 10; static const Duration reconnectDelay = Duration(seconds: 3); } diff --git a/whatsapp_app/lib/controllers/chat_controller.dart b/whatsapp_app/lib/controllers/chat_controller.dart index c98e157..1426adc 100644 --- a/whatsapp_app/lib/controllers/chat_controller.dart +++ b/whatsapp_app/lib/controllers/chat_controller.dart @@ -24,6 +24,8 @@ class ChatController extends GetxController { @override void onInit() { super.onInit(); + _svc.activeChatId.value = conversation.id; + loadMessages(); markAsRead(); @@ -33,6 +35,9 @@ class ChatController extends GetxController { @override void onClose() { + if (_svc.activeChatId.value == conversation.id) { + _svc.activeChatId.value = null; + } _eventSub?.cancel(); inputCtrl.dispose(); scrollCtrl.dispose(); diff --git a/whatsapp_app/lib/main.dart b/whatsapp_app/lib/main.dart index 7bfe0a6..bc18c9b 100644 --- a/whatsapp_app/lib/main.dart +++ b/whatsapp_app/lib/main.dart @@ -1,19 +1,31 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'services/whatsapp_service.dart'; +import 'services/firebase_service.dart'; import 'screens/conversations_screen.dart'; import 'theme/app_theme.dart'; -void main() { +void main() async { WidgetsFlutterBinding.ensureInitialized(); + + // Initialize Firebase (Requires flutterfire configure) + try { + await Firebase.initializeApp(); + } catch (e) { + print('Firebase initialization error: $e'); + } + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.light, )); - // Register the WhatsApp WebSocket client service before app starts + // Register services before app starts Get.put(WhatsAppService(), permanent: true); + Get.put(FirebaseService(), permanent: true); + Get.find().init(); runApp(const WhatsAppApp()); } diff --git a/whatsapp_app/lib/services/firebase_service.dart b/whatsapp_app/lib/services/firebase_service.dart new file mode 100644 index 0000000..8b598fe --- /dev/null +++ b/whatsapp_app/lib/services/firebase_service.dart @@ -0,0 +1,120 @@ +import 'dart:convert'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:get/get.dart'; +import '../models/conversation_model.dart'; +import '../screens/chat_screen.dart'; +import 'whatsapp_service.dart'; + +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // We can't access GetX services easily here since it's a separate isolate, + // but Firebase automatically shows the notification. + print("Handling a background message: ${message.messageId}"); +} + +class FirebaseService extends GetxService { + final FirebaseMessaging _messaging = FirebaseMessaging.instance; + final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin(); + + Future init() async { + // 1. Request Permission + await _messaging.requestPermission( + alert: true, + badge: true, + sound: true, + ); + + // 2. Initialize Local Notifications (for foreground) + const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher'); + const iosInit = DarwinInitializationSettings(); + const initSettings = InitializationSettings(android: androidInit, iOS: iosInit); + + await _localNotifications.initialize( + initSettings, + onDidReceiveNotificationResponse: _onNotificationTap, + ); + + // 3. Background Handler + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + // 4. Foreground Message Handler + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + final chatId = message.data['chatId']; + final activeChatId = Get.find().activeChatId.value; + + // Smart Notification: Only show if we are NOT currently in this chat + if (chatId != null && activeChatId == chatId) { + print('[FCM] Silent notification, user is currently in chat $chatId'); + return; // Silent + } + + _showLocalNotification(message); + }); + + // 5. Handle tapping on a background notification when app opens + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + _handleNotificationClick(message.data); + }); + + // 6. Check if app was opened from a terminated state via a notification + final initialMessage = await _messaging.getInitialMessage(); + if (initialMessage != null) { + Future.delayed(const Duration(seconds: 1), () { + _handleNotificationClick(initialMessage.data); + }); + } + + return this; + } + + void _showLocalNotification(RemoteMessage message) { + final notification = message.notification; + if (notification == null) return; + + const androidDetails = AndroidNotificationDetails( + 'whatsapp_channel', + 'WhatsApp Messages', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker', + ); + const iosDetails = DarwinNotificationDetails(); + const details = NotificationDetails(android: androidDetails, iOS: iosDetails); + + _localNotifications.show( + notification.hashCode, + notification.title, + notification.body, + details, + payload: jsonEncode(message.data), + ); + } + + void _onNotificationTap(NotificationResponse response) { + if (response.payload != null) { + final data = jsonDecode(response.payload!); + _handleNotificationClick(data); + } + } + + void _handleNotificationClick(Map data) { + final chatId = data['chatId']; + final name = data['name'] ?? 'Chat'; + + if (chatId != null) { + // Mock a conversation model to navigate to ChatScreen + final dummyChat = ConversationModel( + id: chatId, + name: name, + isGroup: false, + unreadCount: 0, + timestamp: 0, + pinned: false, + isMuted: false, + ); + + Get.to(() => ChatScreen(conversation: dummyChat)); + } + } +} diff --git a/whatsapp_app/lib/services/whatsapp_service.dart b/whatsapp_app/lib/services/whatsapp_service.dart index 4407fb3..657c3f4 100644 --- a/whatsapp_app/lib/services/whatsapp_service.dart +++ b/whatsapp_app/lib/services/whatsapp_service.dart @@ -11,6 +11,7 @@ class WhatsAppService extends GetxService { final status = WsStatus.disconnected.obs; final qrData = Rx(null); final isWaReady = false.obs; + final activeChatId = Rx(null); // ── Internal ───────────────────────────────────────────────────────────── WebSocketChannel? _channel; diff --git a/whatsapp_app/pubspec.lock b/whatsapp_app/pubspec.lock index d1f1650..3a6f901 100644 --- a/whatsapp_app/pubspec.lock +++ b/whatsapp_app/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + url: "https://pub.dev" + source: hosted + version: "1.3.35" args: dependency: transitive description: @@ -29,10 +37,10 @@ packages: dependency: "direct main" description: name: cached_network_image - sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.4.0" cached_network_image_platform_interface: dependency: transitive description: @@ -45,10 +53,10 @@ packages: dependency: transitive description: name: cached_network_image_web - sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.0" characters: dependency: transitive description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" fake_async: dependency: transitive description: @@ -121,6 +137,54 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + url: "https://pub.dev" + source: hosted + version: "2.32.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb" + url: "https://pub.dev" + source: hosted + version: "5.4.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88" + url: "https://pub.dev" + source: hosted + version: "2.17.5" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: a1662cc95d9750a324ad9df349b873360af6f11414902021f130c68ec02267c4 + url: "https://pub.dev" + source: hosted + version: "14.9.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "87c4a922cb6f811cfb7a889bdbb3622702443c52a0271636cbc90d813ceac147" + url: "https://pub.dev" + source: hosted + version: "4.5.37" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d34dca01a7b103ed7f20138bffbb28eb0e61a677bf9e78a028a932e2c7322d5" + url: "https://pub.dev" + source: hosted + version: "3.8.7" fixnum: dependency: transitive description: @@ -150,6 +214,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -376,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" platform: dependency: transitive description: @@ -581,6 +677,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.1" + timezone: + dependency: transitive + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" typed_data: dependency: transitive description: @@ -617,10 +721,10 @@ packages: dependency: transitive description: name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "0.5.1" web_socket_channel: dependency: "direct main" description: @@ -637,6 +741,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" yaml: dependency: transitive description: diff --git a/whatsapp_app/pubspec.yaml b/whatsapp_app/pubspec.yaml index 0121293..28db806 100644 --- a/whatsapp_app/pubspec.yaml +++ b/whatsapp_app/pubspec.yaml @@ -17,6 +17,9 @@ dependencies: intl: ^0.19.0 timeago: ^3.6.1 shared_preferences: ^2.2.2 + firebase_core: ^2.31.1 + firebase_messaging: ^14.9.1 + flutter_local_notifications: ^17.1.2 dev_dependencies: flutter_test: diff --git a/whatsapp_bridge/server.js b/whatsapp_bridge/server.js index 53d09c4..bf457d3 100644 --- a/whatsapp_bridge/server.js +++ b/whatsapp_bridge/server.js @@ -124,6 +124,7 @@ function initWhatsApp() { waClient = new Client({ authStrategy: new LocalAuth({ clientId: 'whatsapp-bridge' }), puppeteer: { + executablePath: process.env.CHROME_BIN || '/usr/bin/chromium', headless: 'new', args: [ '--no-sandbox',