From feb9335e2944300069b1f67b78a1e8d50af91b41 Mon Sep 17 00:00:00 2001 From: Rafael <1024481@stud.hs-mannheim.de> Date: Mon, 6 May 2024 15:21:21 +0200 Subject: [PATCH] UserDataPage: gender and languages --- lib/constants.dart | 2 + lib/models/language.dart | 6 + lib/pages/user_data_page.dart | 211 ++++++++++++++++++++++++++-------- 3 files changed, 170 insertions(+), 49 deletions(-) diff --git a/lib/constants.dart b/lib/constants.dart index 150b94c..27dbea1 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -8,4 +8,6 @@ class Constants { static const String dbCollectionLanguages = 'languages'; static const String dbCollectionChatRooms = 'chat_rooms'; static const String dbCollectionMessages = 'messages'; + + static const String pathLanguagesJson = 'lib/assets/languages.json'; } diff --git a/lib/models/language.dart b/lib/models/language.dart index f7e63f1..fe872a8 100644 --- a/lib/models/language.dart +++ b/lib/models/language.dart @@ -20,4 +20,10 @@ class Language { 'iconFile': iconFile, }; } + + @override + int get hashCode => code.hashCode; + + @override + bool operator ==(Object other) => other is Language && code == other.code; } diff --git a/lib/pages/user_data_page.dart b/lib/pages/user_data_page.dart index bcb28f7..286a40f 100644 --- a/lib/pages/user_data_page.dart +++ b/lib/pages/user_data_page.dart @@ -17,10 +17,11 @@ class UserDataPage extends StatefulWidget { State createState() => _UserDataPageState(); } +enum Gender { none, male, female, divers } + class _UserDataPageState extends State { final TextEditingController _locationController = TextEditingController(); final TextEditingController _birthdayController = TextEditingController(); - final TextEditingController _genderController = TextEditingController(); List languagesList = []; final List _selectedLanguages = []; @@ -29,37 +30,141 @@ class _UserDataPageState extends State { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final AuthService _authService = AuthService(); + Gender genderView = Gender.none; + int _genderFromDb = 0; + List _languagesFromDb = []; + @override void initState() { super.initState(); - // Load data from JSON file when the widget initializes - loadLanguagesFromJson(); + + // load settings from database + _fetchSettings(); + } + + Future _fetchSettings() async { + try { + // Fetch user ID + String currentUserId = _authService.getCurrentUser()!.uid; + + // Fetch user document fields (email, uid, gender, ...) from database + DocumentSnapshot userSnapshot = await _firestore + .collection(Constants.dbCollectionUsers) + .doc(_authService.getCurrentUser()!.uid) + .get(); + + // Extract gender + setState(() { + _genderFromDb = userSnapshot['gender']; + genderView = Gender.values[_genderFromDb]; + }); + + // Fetch languages + QuerySnapshot languagesSnapshot = await _firestore + .collection(Constants.dbCollectionUsers) + .doc(currentUserId) + .collection(Constants.dbCollectionLanguages) + .get(); + + List userLanguages = []; + for (var doc in languagesSnapshot.docs) { + //languages.add(Language.fromDocument(doc)); + userLanguages.add(Language( + code: doc.id, // aka doc['code'], + name: doc['name'], + nativeName: doc['nativeName'], + iconFile: doc['iconFile'], + )); + } + setState(() { + _languagesFromDb = userLanguages; + }); + + // Load data from JSON file when the widget initializes + loadLanguagesFromJson(); + } catch (error) { + print("Error fetching settings: $error"); + } + } + + /// Loads the languages to display + Future loadLanguagesFromJson() async { + // Load JSON data from assets + String jsonData = await rootBundle.loadString(Constants.pathLanguagesJson); + // Parse JSON into a list of objects + List jsonList = await json.decode(jsonData); + + // Create LanguageSetting objects from JSON data + languagesList = jsonList.map((item) { + Language language = Language( + code: item['code'], + name: item['name'], + nativeName: item['nativeName'], + iconFile: item['iconFile'], + ); + return LanguageSetting( + language: language, + isSelected: _languagesFromDb.any( + (language) => language.code == item['code'], + ), + ); + }).toList(); + + // Update the UI after loading data + setState(() { + if (_selectedLanguages.isEmpty) { + _selectedLanguages.addAll(_languagesFromDb); + } + }); } void saveUserData(BuildContext context) async { - // get userID from auth service + // Get userID from auth service String currentUserID = _authService.getCurrentUser()!.uid; - // Get reference to the Firebase collection - CollectionReference languagesRef = _firestore - .collection(Constants.dbCollectionUsers) - .doc(currentUserID) - .collection(Constants.dbCollectionLanguages); + // Get references to the current users Firebase collections + DocumentReference userRef = + _firestore.collection(Constants.dbCollectionUsers).doc(currentUserID); + CollectionReference languagesRef = + userRef.collection(Constants.dbCollectionLanguages); - // Loop through each Language object and save it to the collection - for (int i = 0; i < _selectedLanguages.length; i++) { - // Convert Language object to a map and add the map to the collection, - // using .doc(myID).set() instead of .add() to avoid duplicates. - await languagesRef - .doc(_selectedLanguages[i].code) - .set(_selectedLanguages[i].toMap()); + // Update Gender in database - only if value has changed + if (_genderFromDb != genderView.index) { + await userRef.update( + {'gender': genderView.index}, + ); + // update local value + _genderFromDb = genderView.index; + } else { + print("gender did NOT change"); } - // List to store language codes from the provided list - List languageCodes = - _selectedLanguages.map((language) => language.code).toList(); - // Clean up languages that were not part of the provided list - await deleteUnusedDocuments(languagesRef, languageCodes); + // Save Languages - only if selected values changed + if (_selectedLanguages.isEmpty) { + print("no language selected"); + } else if (_languagesFromDb.length != _selectedLanguages.length || + _selectedLanguages + .any((element) => !_languagesFromDb.contains(element))) { + // Loop through each Language object and save it to the collection + for (int i = 0; i < _selectedLanguages.length; i++) { + // Convert Language object to a map and add the map to the collection, + // using .doc(myID).set() instead of .add() to avoid duplicates. + await languagesRef + .doc(_selectedLanguages[i].code) + .set(_selectedLanguages[i].toMap()); + } + // update local variable (only if selection is not empty) + _languagesFromDb.clear(); + _languagesFromDb.addAll(_selectedLanguages); + + // List to store language codes from the provided list + List languageCodes = + _selectedLanguages.map((language) => language.code).toList(); + // Clean up languages that were not part of the provided list + await deleteUnusedDocuments(languagesRef, languageCodes); + } else { + print("languages did NOT change"); + } } /// Deletes documents from collection that are not part of the provided list @@ -78,27 +183,12 @@ class _UserDataPageState extends State { } } - Future loadLanguagesFromJson() async { - // Load JSON data from assets - String jsonData = await rootBundle.loadString('lib/assets/languages.json'); - // Parse JSON into a list of objects - List jsonList = await json.decode(jsonData); - - // Create LanguageSetting objects from JSON data - languagesList = jsonList.map((item) { - Language language = Language( - code: item['code'], - name: item['name'], - nativeName: item['nativeName'], - iconFile: item['iconFile'], - ); - // TODO Werte aus DB laden - return LanguageSetting(language: language); - }).toList(); - - // Update the UI after loading data + void updateSelectedGender(Set newSelection) { setState(() { - // TODO ? + // By default there is only a single segment that can be + // selected at one time, so its value is always the first + // item in the selected set. + genderView = newSelection.first; }); } @@ -132,14 +222,37 @@ class _UserDataPageState extends State { obscureText: false, controller: _birthdayController, ), - const Text( - 'Gender', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + Text( + 'Gender (${genderView.name} selected)', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), - MyTextField( - hintText: "Gender", - obscureText: false, - controller: _genderController, + SegmentedButton( + style: SegmentedButton.styleFrom( + selectedBackgroundColor: Colors.blue, + ), + segments: const >[ + ButtonSegment( + value: Gender.none, + label: Text('none'), + ), + ButtonSegment( + value: Gender.male, + label: Text('male'), + //icon: Icon(Icons.male_sharp), + ), + ButtonSegment( + value: Gender.female, + label: Text('female'), + //icon: Icon(Icons.female_sharp), + ), + ButtonSegment( + value: Gender.divers, + label: Text('divers'), + ), + ], + selected: {genderView}, + showSelectedIcon: false, + onSelectionChanged: updateSelectedGender, ), const Divider(), Text( @@ -181,7 +294,7 @@ class _UserDataPageState extends State { required VoidCallback onClicked, }) => ListTile( - tileColor: Colors.greenAccent, + tileColor: Theme.of(context).colorScheme.secondary, onTap: onClicked, leading: SizedBox( width: 48,