feat: Propagate user profile updates to group member details and remove trip code sharing UI.
Some checks failed
Deploy to Play Store / build_and_deploy (push) Has been cancelled

This commit is contained in:
Van Leemput Dayron
2025-12-06 16:43:40 +01:00
parent 13933fc56c
commit bf48971dc4
7 changed files with 107 additions and 137 deletions

View File

@@ -1,6 +1,8 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.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/notification_service.dart';
import 'package:travel_mate/services/logger_service.dart'; import 'package:travel_mate/services/logger_service.dart';
import 'package:travel_mate/services/error_service.dart'; import 'package:travel_mate/services/error_service.dart';
@@ -19,6 +21,8 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
/// Firestore instance for user data operations. /// Firestore instance for user data operations.
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final GroupRepository _groupRepository = GroupRepository();
final _errorService = ErrorService(); final _errorService = ErrorService();
/// Creates a new [UserBloc] with initial state. /// Creates a new [UserBloc] with initial state.
@@ -164,6 +168,16 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
}); });
emit(state.UserLoaded(updatedUser)); 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) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'UserBloc', 'UserBloc',

View File

@@ -657,38 +657,6 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
_handleLeaveTrip(currentUser); _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),
);
},
),
),
);
}
},
),
], ],
), ),
); );

View File

@@ -11,6 +11,13 @@ class PoliciesContent extends StatelessWidget {
} }
} }
Future<void> _launchCustomPrivacyPolicy() async {
final Uri url = Uri.parse('https://xeewy.be/travelmate/policies');
if (!await launchUrl(url)) {
throw Exception('Could not launch $url');
}
}
Future<void> _onBackPressed(BuildContext context) async { Future<void> _onBackPressed(BuildContext context) async {
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
Navigator.pop(context); Navigator.pop(context);
@@ -44,108 +51,8 @@ class PoliciesContent extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Section Collecte d'informations // Keep only the buttons
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],
),
),
const SizedBox(height: 20), 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 // Bouton Google Privacy Policy
SizedBox( SizedBox(
width: double.infinity, 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), const SizedBox(height: 32),
], ],
), ),

View File

@@ -3,9 +3,33 @@ import 'package:travel_mate/components/settings/policies/policies_content.dart';
import 'theme/settings_theme_content.dart'; import 'theme/settings_theme_content.dart';
import 'profile/profile_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}); const SettingsContent({super.key});
@override
State<SettingsContent> createState() => _SettingsContentState();
}
class _SettingsContentState extends State<SettingsContent> {
String _version = '...';
@override
void initState() {
super.initState();
_loadVersion();
}
Future<void> _loadVersion() async {
final packageInfo = await PackageInfo.fromPlatform();
if (mounted) {
setState(() {
_version = packageInfo.version;
});
}
}
Future<void> changePage(BuildContext context, Widget page) async { Future<void> changePage(BuildContext context, Widget page) async {
Navigator.push(context, MaterialPageRoute(builder: (context) => page)); Navigator.push(context, MaterialPageRoute(builder: (context) => page));
} }
@@ -70,7 +94,7 @@ class SettingsContent extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'1.0.0', _version,
style: TextStyle(fontSize: 14, color: Colors.grey[500]), style: TextStyle(fontSize: 14, color: Colors.grey[500]),
), ),
], ],

View File

@@ -286,4 +286,42 @@ class GroupRepository {
.toList(), .toList(),
); );
} }
Future<void> 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 = <String, dynamic>{};
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"
}
}
} }

View File

@@ -1089,7 +1089,7 @@ packages:
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
package_info_plus: package_info_plus:
dependency: transitive dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"

View File

@@ -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 # 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 # 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. # 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: environment:
sdk: ^3.9.2 sdk: ^3.9.2
@@ -63,6 +63,7 @@ dependencies:
image: ^4.5.4 image: ^4.5.4
firebase_messaging: ^16.0.4 firebase_messaging: ^16.0.4
flutter_local_notifications: ^19.5.0 flutter_local_notifications: ^19.5.0
package_info_plus: ^8.3.1
dev_dependencies: dev_dependencies:
flutter_launcher_icons: ^0.13.1 flutter_launcher_icons: ^0.13.1