feat: Add map navigation, enhance FCM deep linking, localize Google Places API, and refine activity display.

This commit is contained in:
Van Leemput Dayron
2025-12-04 11:24:30 +01:00
parent cf4c6447dd
commit e174c1274d
13 changed files with 991 additions and 383 deletions

View File

@@ -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';

View 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();
}
}

View File

@@ -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.