Sync update: 2026-05-18 15:45:06
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
class AppConfig {
|
||||
static const String serverHost = "mywhatsapp.intaleqapp.com";
|
||||
static const int serverPort = 3025;
|
||||
static const String wsUrl = "ws://$serverHost:$serverPort";
|
||||
static const String wsUrl = "wss://$serverHost";
|
||||
|
||||
static const int maxReconnectAttempts = 10;
|
||||
static const Duration reconnectDelay = Duration(seconds: 3);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../services/whatsapp_service.dart';
|
||||
import '../models/conversation_model.dart';
|
||||
|
||||
@@ -18,6 +20,9 @@ class ConversationsController extends GetxController {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// Load local cached conversations first for instant UI response
|
||||
_loadCachedConversations();
|
||||
|
||||
// Load conversations initially if already ready
|
||||
if (_svc.isWaReady.value) {
|
||||
loadConversations();
|
||||
@@ -44,9 +49,34 @@ class ConversationsController extends GetxController {
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// ── Local Caching ────────────────────────────────────────────────────────
|
||||
Future<void> _loadCachedConversations() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cached = prefs.getString('cached_conversations');
|
||||
if (cached != null) {
|
||||
final List<dynamic> decoded = jsonDecode(cached);
|
||||
conversations.assignAll(decoded.map((c) => ConversationModel.fromJson(c as Map<String, dynamic>)));
|
||||
print('[CACHE] Loaded ${conversations.length} conversations instantly.');
|
||||
}
|
||||
} catch (e) {
|
||||
print('[CACHE ERROR] $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveConversationsToCache(List<dynamic> rawData) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('cached_conversations', jsonEncode(rawData));
|
||||
print('[CACHE] Saved ${rawData.length} conversations to local cache.');
|
||||
} catch (e) {
|
||||
print('[CACHE SAVE ERROR] $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load Conversations ───────────────────────────────────────────────────
|
||||
Future<void> loadConversations() async {
|
||||
if (!_svc.isWaReady.value) return;
|
||||
if (!_svc.isWaReady.value || isLoading.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
errorMessage.value = null;
|
||||
@@ -56,6 +86,7 @@ class ConversationsController extends GetxController {
|
||||
if (res['type'] == 'conversations') {
|
||||
final List<dynamic> data = res['data'] ?? [];
|
||||
conversations.assignAll(data.map((c) => ConversationModel.fromJson(c as Map<String, dynamic>)));
|
||||
_saveConversationsToCache(data);
|
||||
} else {
|
||||
errorMessage.value = res['message'] ?? 'Failed to load conversations';
|
||||
}
|
||||
@@ -159,6 +190,7 @@ class ConversationsController extends GetxController {
|
||||
if (res['type'] == 'conversations') {
|
||||
final List<dynamic> data = res['data'] ?? [];
|
||||
conversations.assignAll(data.map((c) => ConversationModel.fromJson(c as Map<String, dynamic>)));
|
||||
_saveConversationsToCache(data);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
86
whatsapp_app/lib/firebase_options.dart
Normal file
86
whatsapp_app/lib/firebase_options.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
return macos;
|
||||
case TargetPlatform.windows:
|
||||
return windows;
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBwxlFBkdejkm_H5mMAeTK30fuReZ4EeRU',
|
||||
appId: '1:146827764316:web:99b4d29684ee9a26843c99',
|
||||
messagingSenderId: '146827764316',
|
||||
projectId: 'mywhatsapp-inta',
|
||||
authDomain: 'mywhatsapp-inta.firebaseapp.com',
|
||||
storageBucket: 'mywhatsapp-inta.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBqCFyXUfd1VjX3htG0Z_-gH4hFOkG31C0',
|
||||
appId: '1:146827764316:android:75425f8baccb6891843c99',
|
||||
messagingSenderId: '146827764316',
|
||||
projectId: 'mywhatsapp-inta',
|
||||
storageBucket: 'mywhatsapp-inta.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDZhiD8lCsi5fa-IB199rY4C3MoYn2X4hQ',
|
||||
appId: '1:146827764316:ios:c240ccd825bdb66d843c99',
|
||||
messagingSenderId: '146827764316',
|
||||
projectId: 'mywhatsapp-inta',
|
||||
storageBucket: 'mywhatsapp-inta.firebasestorage.app',
|
||||
iosBundleId: 'com.intaleqapp.mywhatsapp',
|
||||
);
|
||||
|
||||
static const FirebaseOptions macos = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDZhiD8lCsi5fa-IB199rY4C3MoYn2X4hQ',
|
||||
appId: '1:146827764316:ios:c240ccd825bdb66d843c99',
|
||||
messagingSenderId: '146827764316',
|
||||
projectId: 'mywhatsapp-inta',
|
||||
storageBucket: 'mywhatsapp-inta.firebasestorage.app',
|
||||
iosBundleId: 'com.intaleqapp.mywhatsapp',
|
||||
);
|
||||
|
||||
static const FirebaseOptions windows = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBwxlFBkdejkm_H5mMAeTK30fuReZ4EeRU',
|
||||
appId: '1:146827764316:web:8812ccd133ed647e843c99',
|
||||
messagingSenderId: '146827764316',
|
||||
projectId: 'mywhatsapp-inta',
|
||||
authDomain: 'mywhatsapp-inta.firebaseapp.com',
|
||||
storageBucket: 'mywhatsapp-inta.firebasestorage.app',
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
@@ -15,7 +16,8 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
|
||||
class FirebaseService extends GetxService {
|
||||
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
||||
final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
Future<FirebaseService> init() async {
|
||||
// 1. Request Permission
|
||||
@@ -28,8 +30,9 @@ class FirebaseService extends GetxService {
|
||||
// 2. Initialize Local Notifications (for foreground)
|
||||
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const iosInit = DarwinInitializationSettings();
|
||||
const initSettings = InitializationSettings(android: androidInit, iOS: iosInit);
|
||||
|
||||
const initSettings =
|
||||
InitializationSettings(android: androidInit, iOS: iosInit);
|
||||
|
||||
await _localNotifications.initialize(
|
||||
initSettings,
|
||||
onDidReceiveNotificationResponse: _onNotificationTap,
|
||||
@@ -65,9 +68,48 @@ class FirebaseService extends GetxService {
|
||||
});
|
||||
}
|
||||
|
||||
// 7. Request and register FCM Token on the server
|
||||
_setupFcmTokenRegistration();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void _setupFcmTokenRegistration() async {
|
||||
try {
|
||||
final token = await _messaging.getToken();
|
||||
if (token != null) {
|
||||
print('[FCM] Token obtained: ${token.substring(0, 15)}...');
|
||||
_registerTokenOnServer(token);
|
||||
}
|
||||
} catch (e) {
|
||||
print('[FCM ERROR] Could not get token: $e');
|
||||
}
|
||||
|
||||
_messaging.onTokenRefresh.listen((newToken) {
|
||||
print('[FCM] Token refreshed');
|
||||
_registerTokenOnServer(newToken);
|
||||
});
|
||||
}
|
||||
|
||||
void _registerTokenOnServer(String token) {
|
||||
final wsSvc = Get.find<WhatsAppService>();
|
||||
if (wsSvc.status.value == WsStatus.waReady ||
|
||||
wsSvc.status.value == WsStatus.connected) {
|
||||
print('[FCM] Sending token to server...');
|
||||
wsSvc.sendFcmToken(token);
|
||||
} else {
|
||||
// Listen to status changes and send once connected
|
||||
late StreamSubscription sub;
|
||||
sub = wsSvc.status.listen((status) {
|
||||
if (status == WsStatus.waReady || status == WsStatus.connected) {
|
||||
print('[FCM] WebSocket connected, sending token to server...');
|
||||
wsSvc.sendFcmToken(token);
|
||||
sub.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _showLocalNotification(RemoteMessage message) {
|
||||
final notification = message.notification;
|
||||
if (notification == null) return;
|
||||
@@ -80,7 +122,8 @@ class FirebaseService extends GetxService {
|
||||
ticker: 'ticker',
|
||||
);
|
||||
const iosDetails = DarwinNotificationDetails();
|
||||
const details = NotificationDetails(android: androidDetails, iOS: iosDetails);
|
||||
const details =
|
||||
NotificationDetails(android: androidDetails, iOS: iosDetails);
|
||||
|
||||
_localNotifications.show(
|
||||
notification.hashCode,
|
||||
@@ -101,7 +144,7 @@ class FirebaseService extends GetxService {
|
||||
void _handleNotificationClick(Map<String, dynamic> data) {
|
||||
final chatId = data['chatId'];
|
||||
final name = data['name'] ?? 'Chat';
|
||||
|
||||
|
||||
if (chatId != null) {
|
||||
// Mock a conversation model to navigate to ChatScreen
|
||||
final dummyChat = ConversationModel(
|
||||
@@ -113,7 +156,7 @@ class FirebaseService extends GetxService {
|
||||
pinned: false,
|
||||
isMuted: false,
|
||||
);
|
||||
|
||||
|
||||
Get.to(() => ChatScreen(conversation: dummyChat));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ class WhatsAppService extends GetxService {
|
||||
|
||||
status.value = WsStatus.connecting;
|
||||
_reconnectTimer?.cancel();
|
||||
print('[WS] Attempting to connect to ${AppConfig.wsUrl}...');
|
||||
|
||||
try {
|
||||
_channel = WebSocketChannel.connect(Uri.parse(AppConfig.wsUrl));
|
||||
@@ -62,15 +63,18 @@ class WhatsAppService extends GetxService {
|
||||
);
|
||||
status.value = WsStatus.connected;
|
||||
_reconnectCount = 0;
|
||||
print('[WS] Connected successfully.');
|
||||
|
||||
// Request initial status check
|
||||
ping();
|
||||
} catch (e) {
|
||||
print('[WS] Connection exception: $e');
|
||||
_scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void _onData(dynamic raw) {
|
||||
print('[WS RECV] $raw');
|
||||
Map<String, dynamic> data;
|
||||
try {
|
||||
data = jsonDecode(raw as String);
|
||||
@@ -127,10 +131,12 @@ class WhatsAppService extends GetxService {
|
||||
}
|
||||
|
||||
void _onError(Object err) {
|
||||
print('[WS ERROR] $err');
|
||||
_handleDisconnect();
|
||||
}
|
||||
|
||||
void _onDone() {
|
||||
print('[WS DONE] Connection closed by server');
|
||||
_handleDisconnect();
|
||||
}
|
||||
|
||||
@@ -169,16 +175,18 @@ class WhatsAppService extends GetxService {
|
||||
_pending[id] = completer;
|
||||
|
||||
try {
|
||||
_channel?.sink.add(jsonEncode(payload));
|
||||
final jsonMsg = jsonEncode(payload);
|
||||
print('[WS SEND] $jsonMsg');
|
||||
_channel?.sink.add(jsonMsg);
|
||||
} catch (e) {
|
||||
_pending.remove(id);
|
||||
completer.completeError(e);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
// Timeout after 15s
|
||||
// Timeout after 60s
|
||||
return completer.future.timeout(
|
||||
const Duration(seconds: 15),
|
||||
const Duration(seconds: 60),
|
||||
onTimeout: () {
|
||||
_pending.remove(id);
|
||||
throw TimeoutException('Request timed out: ${payload['type']}');
|
||||
@@ -204,6 +212,9 @@ class WhatsAppService extends GetxService {
|
||||
Future<Map<String, dynamic>> searchConversations(String query) =>
|
||||
_request({ 'type': 'search_conversations', 'query': query });
|
||||
|
||||
Future<Map<String, dynamic>> sendFcmToken(String token) =>
|
||||
_request({ 'type': 'register_fcm', 'token': token });
|
||||
|
||||
Future<Map<String, dynamic>> ping() =>
|
||||
_request({ 'type': 'ping' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user