From bf48971dc4c714fda2b9cca6174fe55f89082e97 Mon Sep 17 00:00:00 2001 From: Van Leemput Dayron Date: Sat, 6 Dec 2025 16:43:40 +0100 Subject: [PATCH] feat: Propagate user profile updates to group member details and remove trip code sharing UI. --- lib/blocs/user/user_bloc.dart | 14 ++ .../home/show_trip_details_content.dart | 32 ----- .../settings/policies/policies_content.dart | 127 ++++-------------- lib/components/settings/settings_content.dart | 28 +++- lib/repositories/group_repository.dart | 38 ++++++ pubspec.lock | 2 +- pubspec.yaml | 3 +- 7 files changed, 107 insertions(+), 137 deletions(-) diff --git a/lib/blocs/user/user_bloc.dart b/lib/blocs/user/user_bloc.dart index 83d5aec..472b53e 100644 --- a/lib/blocs/user/user_bloc.dart +++ b/lib/blocs/user/user_bloc.dart @@ -1,6 +1,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; + +import 'package:travel_mate/repositories/group_repository.dart'; import 'package:travel_mate/services/notification_service.dart'; import 'package:travel_mate/services/logger_service.dart'; import 'package:travel_mate/services/error_service.dart'; @@ -19,6 +21,8 @@ class UserBloc extends Bloc { /// Firestore instance for user data operations. final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final GroupRepository _groupRepository = GroupRepository(); + final _errorService = ErrorService(); /// Creates a new [UserBloc] with initial state. @@ -164,6 +168,16 @@ class UserBloc extends Bloc { }); emit(state.UserLoaded(updatedUser)); + + // Propager les changements aux groupes + await _groupRepository.updateMemberDetails( + userId: currentUser.id, + firstName: event + .userData['prenom'], // 'prenom' dans Firestore map map to firstName usually? Wait, UserModel has prenom/nom. + lastName: event.userData['nom'], + profilePictureUrl: event + .userData['profilePictureUrl'], // Key was 'profilePictureUrl' in ProfileContent + ); } catch (e, stackTrace) { _errorService.logError( 'UserBloc', diff --git a/lib/components/home/show_trip_details_content.dart b/lib/components/home/show_trip_details_content.dart index 64d127a..fb6e62a 100644 --- a/lib/components/home/show_trip_details_content.dart +++ b/lib/components/home/show_trip_details_content.dart @@ -657,38 +657,6 @@ class _ShowTripDetailsContentState extends State { _handleLeaveTrip(currentUser); }, ), - ListTile( - leading: Icon( - Icons.share, - color: theme.colorScheme.onSurface, - ), - title: Text( - 'Partager le code', - style: theme.textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.onSurface, - ), - ), - onTap: () { - Navigator.pop(context); - // Implement share functionality - if (_group != null) { - // Use share_plus package to share the code - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('ID du groupe : ${_group!.id}'), - action: SnackBarAction( - label: 'Copier', - onPressed: () { - Clipboard.setData( - ClipboardData(text: _group!.id), - ); - }, - ), - ), - ); - } - }, - ), ], ), ); diff --git a/lib/components/settings/policies/policies_content.dart b/lib/components/settings/policies/policies_content.dart index 0440a73..f19626b 100644 --- a/lib/components/settings/policies/policies_content.dart +++ b/lib/components/settings/policies/policies_content.dart @@ -11,6 +11,13 @@ class PoliciesContent extends StatelessWidget { } } + Future _launchCustomPrivacyPolicy() async { + final Uri url = Uri.parse('https://xeewy.be/travelmate/policies'); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } + } + Future _onBackPressed(BuildContext context) async { if (Navigator.canPop(context)) { Navigator.pop(context); @@ -44,108 +51,8 @@ class PoliciesContent extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Section Collecte d'informations - Text( - 'Collecte d\'informations', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w600, - color: isDark ? Colors.white : Colors.black, - ), - ), - const SizedBox(height: 10), - Text( - 'Nous collectons des informations que vous nous fournissez directement, comme votre nom, adresse e-mail et préférences de voyage.', - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.grey[300] : Colors.grey[800], - ), - ), + // Keep only the buttons const SizedBox(height: 20), - - // Section Utilisation des données - Text( - 'Utilisation des données', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w600, - color: isDark ? Colors.white : Colors.black, - ), - ), - const SizedBox(height: 10), - Text( - 'Vos données sont utilisées pour améliorer votre expérience utilisateur et vous proposer des recommandations personnalisées.', - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.grey[300] : Colors.grey[800], - ), - ), - const SizedBox(height: 20), - - // Section Protection des données - Text( - 'Protection des données', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w600, - color: isDark ? Colors.white : Colors.black, - ), - ), - const SizedBox(height: 10), - Text( - 'Nous mettons en place des mesures de sécurité appropriées pour protéger vos informations personnelles.', - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.grey[300] : Colors.grey[800], - ), - ), - const SizedBox(height: 20), - - // Section Partage des données - Text( - 'Partage des données', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w600, - color: isDark ? Colors.white : Colors.black, - ), - ), - const SizedBox(height: 10), - Text( - 'Nous ne partageons pas vos informations personnelles avec des tiers sans votre consentement explicite.', - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.grey[300] : Colors.grey[800], - ), - ), - const SizedBox(height: 20), - - // Section Droits de l'utilisateur - Text( - 'Vos droits', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w600, - color: isDark ? Colors.white : Colors.black, - ), - ), - const SizedBox(height: 10), - Text( - 'Vous avez le droit d\'accéder, de corriger ou de supprimer vos données personnelles à tout moment. Veuillez nous contacter pour toute demande.', - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.grey[300] : Colors.grey[800], - ), - ), - const SizedBox(height: 20), - - // Section Contact - Text( - 'Nous contacter', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w600, - color: isDark ? Colors.white : Colors.black, - ), - ), - const SizedBox(height: 10), - Text( - 'Pour toute question concernant cette politique de confidentialité, veuillez nous contacter à support@travelmate.com', - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.grey[300] : Colors.grey[800], - ), - ), - const SizedBox(height: 20), - // Bouton Google Privacy Policy SizedBox( width: double.infinity, @@ -163,6 +70,24 @@ class PoliciesContent extends StatelessWidget { ), ), ), + const SizedBox(height: 12), + // Bouton Nos politiques de confidentialité + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: _launchCustomPrivacyPolicy, + icon: const Icon(Icons.policy), + label: const Text('Nos politiques de confidentialités'), + style: ElevatedButton.styleFrom( + backgroundColor: theme.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), const SizedBox(height: 32), ], ), diff --git a/lib/components/settings/settings_content.dart b/lib/components/settings/settings_content.dart index 7b9f1ff..bd58771 100644 --- a/lib/components/settings/settings_content.dart +++ b/lib/components/settings/settings_content.dart @@ -3,9 +3,33 @@ import 'package:travel_mate/components/settings/policies/policies_content.dart'; import 'theme/settings_theme_content.dart'; import 'profile/profile_content.dart'; -class SettingsContent extends StatelessWidget { +import 'package:package_info_plus/package_info_plus.dart'; + +class SettingsContent extends StatefulWidget { const SettingsContent({super.key}); + @override + State createState() => _SettingsContentState(); +} + +class _SettingsContentState extends State { + String _version = '...'; + + @override + void initState() { + super.initState(); + _loadVersion(); + } + + Future _loadVersion() async { + final packageInfo = await PackageInfo.fromPlatform(); + if (mounted) { + setState(() { + _version = packageInfo.version; + }); + } + } + Future changePage(BuildContext context, Widget page) async { Navigator.push(context, MaterialPageRoute(builder: (context) => page)); } @@ -70,7 +94,7 @@ class SettingsContent extends StatelessWidget { ), const SizedBox(height: 4), Text( - '1.0.0', + _version, style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ], diff --git a/lib/repositories/group_repository.dart b/lib/repositories/group_repository.dart index 7a3eb2c..29eab59 100644 --- a/lib/repositories/group_repository.dart +++ b/lib/repositories/group_repository.dart @@ -286,4 +286,42 @@ class GroupRepository { .toList(), ); } + + Future updateMemberDetails({ + required String userId, + String? firstName, + String? lastName, + String? profilePictureUrl, + }) async { + try { + // 1. Trouver tous les groupes où l'utilisateur est membre + final groupsSnapshot = await _groupsCollection + .where('memberIds', arrayContains: userId) + .get(); + + // 2. Mettre à jour les infos du membre dans chaque groupe + for (final groupDoc in groupsSnapshot.docs) { + final memberRef = _membersCollection(groupDoc.id).doc(userId); + + final updates = {}; + if (firstName != null) updates['firstName'] = firstName; + if (lastName != null) updates['lastName'] = lastName; + if (profilePictureUrl != null) { + updates['profilePictureUrl'] = profilePictureUrl; + } + + if (updates.isNotEmpty) { + await memberRef.update(updates); + } + } + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur update member details: $e', + stackTrace, + ); + // On ne throw pas d'exception ici pour ne pas bloquer l'update user principal + // C'est une opération "best effort" + } + } } diff --git a/pubspec.lock b/pubspec.lock index b17f09c..9600d52 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1089,7 +1089,7 @@ packages: source: hosted version: "2.2.0" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" diff --git a/pubspec.yaml b/pubspec.yaml index 890a0e1..86ff701 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.1+2 +version: 1.0.2+3 environment: sdk: ^3.9.2 @@ -63,6 +63,7 @@ dependencies: image: ^4.5.4 firebase_messaging: ^16.0.4 flutter_local_notifications: ^19.5.0 + package_info_plus: ^8.3.1 dev_dependencies: flutter_launcher_icons: ^0.13.1