feat: Add map navigation, enhance FCM deep linking, localize Google Places API, and refine activity display.
This commit is contained in:
@@ -116,7 +116,7 @@ class ActivityPlacesService {
|
||||
|
||||
final encodedDestination = Uri.encodeComponent(destination);
|
||||
final url =
|
||||
'https://maps.googleapis.com/maps/api/geocode/json?address=$encodedDestination&key=$_apiKey';
|
||||
'https://maps.googleapis.com/maps/api/geocode/json?address=$encodedDestination&key=$_apiKey&language=fr';
|
||||
|
||||
LoggerService.info('ActivityPlacesService: Géocodage de "$destination"');
|
||||
LoggerService.info('ActivityPlacesService: URL = $url');
|
||||
@@ -184,7 +184,8 @@ class ActivityPlacesService {
|
||||
'?location=$lat,$lng'
|
||||
'&radius=$radius'
|
||||
'&type=${category.googlePlaceType}'
|
||||
'&key=$_apiKey';
|
||||
'&key=$_apiKey'
|
||||
'&language=fr';
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
@@ -287,7 +288,8 @@ class ActivityPlacesService {
|
||||
'https://maps.googleapis.com/maps/api/place/details/json'
|
||||
'?place_id=$placeId'
|
||||
'&fields=formatted_address,formatted_phone_number,website,opening_hours,editorial_summary'
|
||||
'&key=$_apiKey';
|
||||
'&key=$_apiKey'
|
||||
'&language=fr';
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
@@ -356,7 +358,8 @@ class ActivityPlacesService {
|
||||
'?query=$encodedQuery in $destination'
|
||||
'&location=${coordinates['lat']},${coordinates['lng']}'
|
||||
'&radius=$radius'
|
||||
'&key=$_apiKey';
|
||||
'&key=$_apiKey'
|
||||
'&language=fr';
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
@@ -513,7 +516,8 @@ class ActivityPlacesService {
|
||||
'?location=$lat,$lng'
|
||||
'&radius=$radius'
|
||||
'&type=${category.googlePlaceType}'
|
||||
'&key=$_apiKey';
|
||||
'&key=$_apiKey'
|
||||
'&language=fr';
|
||||
|
||||
if (nextPageToken != null) {
|
||||
url += '&pagetoken=$nextPageToken';
|
||||
@@ -589,7 +593,8 @@ class ActivityPlacesService {
|
||||
'?location=$lat,$lng'
|
||||
'&radius=$radius'
|
||||
'&type=tourist_attraction'
|
||||
'&key=$_apiKey';
|
||||
'&key=$_apiKey'
|
||||
'&language=fr';
|
||||
|
||||
if (nextPageToken != null) {
|
||||
url += '&pagetoken=$nextPageToken';
|
||||
|
||||
36
lib/services/map_navigation_service.dart
Normal file
36
lib/services/map_navigation_service.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'dart:async';
|
||||
|
||||
class MapLocationRequest {
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final String? name;
|
||||
final DateTime timestamp;
|
||||
|
||||
MapLocationRequest({
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
this.name,
|
||||
}) : timestamp = DateTime.now();
|
||||
}
|
||||
|
||||
class MapNavigationService {
|
||||
final _requestController = StreamController<MapLocationRequest>.broadcast();
|
||||
MapLocationRequest? _lastRequest;
|
||||
|
||||
Stream<MapLocationRequest> get requestStream => _requestController.stream;
|
||||
MapLocationRequest? get lastRequest => _lastRequest;
|
||||
|
||||
void navigateToLocation(double lat, double lng, {String? name}) {
|
||||
final request = MapLocationRequest(
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
name: name,
|
||||
);
|
||||
_lastRequest = request;
|
||||
_requestController.add(request);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_requestController.close();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,12 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:travel_mate/services/logger_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
import 'package:travel_mate/repositories/group_repository.dart';
|
||||
import 'package:travel_mate/repositories/account_repository.dart';
|
||||
import 'package:travel_mate/components/group/chat_group_content.dart';
|
||||
import 'package:travel_mate/components/account/group_expenses_page.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
@@ -43,6 +49,7 @@ class NotificationService {
|
||||
onDidReceiveNotificationResponse: (details) {
|
||||
// Handle notification tap
|
||||
LoggerService.info('Notification tapped: ${details.payload}');
|
||||
// TODO: Handle local notification tap if needed, usually we rely on FCM callbacks
|
||||
},
|
||||
);
|
||||
|
||||
@@ -52,6 +59,10 @@ class NotificationService {
|
||||
// Handle token refresh
|
||||
FirebaseMessaging.instance.onTokenRefresh.listen(_onTokenRefresh);
|
||||
|
||||
// Setup interacted message (Deep Linking)
|
||||
// We don't call this here anymore, it will be called from HomePage
|
||||
// await setupInteractedMessage();
|
||||
|
||||
_isInitialized = true;
|
||||
LoggerService.info('NotificationService initialized');
|
||||
|
||||
@@ -60,6 +71,87 @@ class NotificationService {
|
||||
LoggerService.info('Current FCM Token: $token');
|
||||
}
|
||||
|
||||
/// Sets up the background message listener.
|
||||
/// Should be called when the app is ready to handle navigation.
|
||||
void startListening() {
|
||||
// Handle any interaction when the app is in the background via a
|
||||
// Stream listener
|
||||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
||||
_handleNotificationTap(message.data);
|
||||
});
|
||||
}
|
||||
|
||||
/// Checks for an initial message (app opened from terminated state)
|
||||
/// and handles it if present.
|
||||
Future<void> handleInitialMessage() async {
|
||||
// Get any messages which caused the application to open from
|
||||
// a terminated state.
|
||||
RemoteMessage? initialMessage = await _firebaseMessaging
|
||||
.getInitialMessage();
|
||||
|
||||
if (initialMessage != null) {
|
||||
LoggerService.info('Found initial message: ${initialMessage.data}');
|
||||
_handleNotificationTap(initialMessage.data);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleNotificationTap(Map<String, dynamic> data) async {
|
||||
LoggerService.info('Handling notification tap with data: $data');
|
||||
// DEBUG: Show snackbar to verify payload
|
||||
// ErrorService().showSnackbar(message: 'Debug: Payload $data', isError: false);
|
||||
|
||||
final type = data['type'];
|
||||
|
||||
try {
|
||||
if (type == 'message') {
|
||||
final groupId = data['groupId'];
|
||||
if (groupId != null) {
|
||||
final groupRepository = GroupRepository();
|
||||
final group = await groupRepository.getGroupById(groupId);
|
||||
if (group != null) {
|
||||
ErrorService.navigatorKey.currentState?.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatGroupContent(group: group),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
LoggerService.error('Group not found: $groupId');
|
||||
// ErrorService().showError(message: 'Groupe introuvable: $groupId');
|
||||
}
|
||||
} else {
|
||||
LoggerService.error('Missing groupId in payload');
|
||||
// ErrorService().showError(message: 'Payload invalide: groupId manquant');
|
||||
}
|
||||
} else if (type == 'expense') {
|
||||
final tripId = data['tripId'];
|
||||
if (tripId != null) {
|
||||
final accountRepository = AccountRepository();
|
||||
final groupRepository = GroupRepository();
|
||||
|
||||
final account = await accountRepository.getAccountByTripId(tripId);
|
||||
final group = await groupRepository.getGroupByTripId(tripId);
|
||||
|
||||
if (account != null && group != null) {
|
||||
ErrorService.navigatorKey.currentState?.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
GroupExpensesPage(account: account, group: group),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
LoggerService.error('Account or Group not found for trip: $tripId');
|
||||
// ErrorService().showError(message: 'Compte ou Groupe introuvable');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoggerService.info('Unknown notification type: $type');
|
||||
}
|
||||
} catch (e) {
|
||||
LoggerService.error('Error handling notification tap: $e');
|
||||
ErrorService().showError(message: 'Erreur navigation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onTokenRefresh(String newToken) async {
|
||||
LoggerService.info('FCM Token refreshed: $newToken');
|
||||
// We need the user ID to save the token.
|
||||
|
||||
Reference in New Issue
Block a user