feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import '../../../core/api/api_client.dart';
|
||||
import '../../../core/di.dart';
|
||||
import 'trip_event_merger.dart';
|
||||
|
||||
/// القناة الثانية للزمن الحقيقي — FCM data-message (docs/23 §15).
|
||||
///
|
||||
/// تستقبل رسائل data-only من السيرفر حول تحديثات الرحلة وتُمرّرها
|
||||
/// إلى TripEventMerger بنفس شكل WebSocket — أيّهما يسبق يفوز،
|
||||
/// والـ merger يتكفّل بالتصفية الصامتة.
|
||||
class FcmService {
|
||||
final TripEventMerger _merger;
|
||||
StreamSubscription<RemoteMessage>? _onMessageSub;
|
||||
StreamSubscription<RemoteMessage>? _onOpenSub;
|
||||
|
||||
FcmService(this._merger);
|
||||
|
||||
/// تهيئة FCM بعد تسجيل الدخول — تُستدعى من نقطة واحدة.
|
||||
Future<void> init() async {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
|
||||
await messaging.requestPermission(
|
||||
alert: false,
|
||||
badge: false,
|
||||
sound: false,
|
||||
provisional: false,
|
||||
);
|
||||
|
||||
final token = await messaging.getToken();
|
||||
if (token != null) {
|
||||
_registerToken(token);
|
||||
}
|
||||
messaging.onTokenRefresh.listen(_registerToken);
|
||||
|
||||
_onMessageSub = FirebaseMessaging.onMessage.listen(_handleMessage);
|
||||
_onOpenSub = FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_onMessageSub?.cancel();
|
||||
_onOpenSub?.cancel();
|
||||
}
|
||||
|
||||
void _handleMessage(RemoteMessage message) {
|
||||
final data = message.data;
|
||||
final type = data['type'] as String?;
|
||||
final tripId = data['tripId'] as String?;
|
||||
|
||||
if (type == null || tripId == null || tripId.isEmpty) return;
|
||||
|
||||
final status = type.startsWith('trip_') ? type.substring(5) : null;
|
||||
if (status == null) return;
|
||||
|
||||
final extra = Map<String, dynamic>.from(data)
|
||||
..remove('type')
|
||||
..remove('tripId')
|
||||
..remove('title')
|
||||
..remove('body');
|
||||
|
||||
_merger.addUpdate(TripUpdate(
|
||||
tripId: tripId,
|
||||
status: status,
|
||||
driverId: data['driverId'] as String?,
|
||||
extra: extra,
|
||||
));
|
||||
}
|
||||
|
||||
static Future<void> _registerToken(String token) async {
|
||||
try {
|
||||
final api = getIt<ApiClient>();
|
||||
await api.dio.post('/notifications/token', data: {
|
||||
'token': token,
|
||||
'platform': Platform.isIOS ? 'ios' : 'android',
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user