cofounderella/lib/pages/user_data_page.dart

364 lines
12 KiB
Dart
Raw Normal View History

2024-05-05 01:02:09 +02:00
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cofounderella/components/my_button.dart';
2024-05-05 10:26:49 +02:00
import 'package:cofounderella/constants.dart';
2024-05-05 01:02:09 +02:00
import 'package:cofounderella/models/language.dart';
import 'package:cofounderella/models/language_setting.dart';
2024-05-09 21:55:17 +02:00
import 'package:cofounderella/components/location_selector.dart';
2024-05-05 01:02:09 +02:00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:cofounderella/services/auth/auth_service.dart';
2024-05-09 21:55:17 +02:00
import 'package:cofounderella/models/location.dart';
2024-05-05 01:02:09 +02:00
class UserDataPage extends StatefulWidget {
const UserDataPage({super.key});
@override
State<UserDataPage> createState() => _UserDataPageState();
}
2024-05-06 15:21:21 +02:00
enum Gender { none, male, female, divers }
2024-05-05 01:02:09 +02:00
class _UserDataPageState extends State<UserDataPage> {
2024-05-09 21:55:17 +02:00
MyLocation? _selectedLocation;
2024-05-05 01:02:09 +02:00
List<LanguageSetting> languagesList = [];
final List<Language> _selectedLanguages = [];
// get instance of firestore and auth
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final AuthService _authService = AuthService();
2024-05-08 00:59:16 +02:00
int? _selectedYear;
int? _yearFromDb;
2024-05-06 15:21:21 +02:00
Gender genderView = Gender.none;
int _genderFromDb = 0;
List<Language> _languagesFromDb = [];
2024-05-05 01:02:09 +02:00
@override
void initState() {
super.initState();
2024-05-06 15:21:21 +02:00
// load settings from database
_fetchSettings();
2024-05-05 01:02:09 +02:00
}
2024-05-06 15:21:21 +02:00
Future<void> _fetchSettings() async {
try {
// Fetch user ID
String currentUserId = _authService.getCurrentUser()!.uid;
2024-05-05 01:02:09 +02:00
2024-05-06 15:21:21 +02:00
// Fetch user document fields (email, uid, gender, ...) from database
DocumentSnapshot userSnapshot = await _firestore
.collection(Constants.dbCollectionUsers)
2024-05-08 00:59:16 +02:00
.doc(currentUserId)
2024-05-06 15:21:21 +02:00
.get();
2024-05-05 01:02:09 +02:00
2024-05-08 00:59:16 +02:00
// Extract gender and birth year
_genderFromDb = userSnapshot[Constants.dbFieldUsersGender];
_yearFromDb = userSnapshot
.data()
.toString()
.contains(Constants.dbFieldUsersYearBorn)
? userSnapshot[Constants.dbFieldUsersYearBorn]
: null;
2024-05-05 01:02:09 +02:00
2024-05-06 15:21:21 +02:00
// Fetch languages
QuerySnapshot languagesSnapshot = await _firestore
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.collection(Constants.dbCollectionLanguages)
.get();
2024-05-05 01:02:09 +02:00
2024-05-06 15:21:21 +02:00
List<Language> 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'],
));
2024-05-05 01:02:09 +02:00
}
2024-05-06 15:21:21 +02:00
setState(() {
2024-05-08 00:59:16 +02:00
genderView = Gender.values[_genderFromDb];
_selectedYear = _yearFromDb;
2024-05-06 15:21:21 +02:00
_languagesFromDb = userLanguages;
});
// Load data from JSON file when the widget initializes
loadLanguagesFromJson();
} catch (error) {
print("Error fetching settings: $error");
2024-05-05 01:02:09 +02:00
}
}
2024-05-06 15:21:21 +02:00
/// Loads the languages to display
2024-05-05 01:02:09 +02:00
Future<void> loadLanguagesFromJson() async {
// Load JSON data from assets
2024-05-06 15:21:21 +02:00
String jsonData = await rootBundle.loadString(Constants.pathLanguagesJson);
2024-05-05 01:02:09 +02:00
// Parse JSON into a list of objects
List<dynamic> 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'],
);
2024-05-06 15:21:21 +02:00
return LanguageSetting(
language: language,
isSelected: _languagesFromDb.any(
(language) => language.code == item['code'],
),
);
2024-05-05 01:02:09 +02:00
}).toList();
// Update the UI after loading data
setState(() {
2024-05-06 15:21:21 +02:00
if (_selectedLanguages.isEmpty) {
_selectedLanguages.addAll(_languagesFromDb);
}
});
}
void saveUserData(BuildContext context) async {
// Get userID from auth service
String currentUserID = _authService.getCurrentUser()!.uid;
// Get references to the current users Firebase collections
DocumentReference userRef =
_firestore.collection(Constants.dbCollectionUsers).doc(currentUserID);
CollectionReference languagesRef =
userRef.collection(Constants.dbCollectionLanguages);
2024-05-08 00:59:16 +02:00
if (_selectedYear != _yearFromDb) {
await userRef.update(
{'born': _selectedYear},
);
// update local value
_yearFromDb = _selectedYear;
} else {
print("birth year did NOT change");
}
2024-05-06 15:21:21 +02:00
// 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");
}
// 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<String> 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
Future<void> deleteUnusedDocuments(
CollectionReference refCollection, List<String> idsToKeep) async {
// Fetch the existing documents from the database
QuerySnapshot snapshot = await refCollection.get();
// Loop through each document in the collection
for (QueryDocumentSnapshot doc in snapshot.docs) {
String documentId = doc.id;
// If the language code is not in the provided list, delete the document
if (!idsToKeep.contains(documentId)) {
await doc.reference.delete();
}
}
}
void updateSelectedGender(Set<Gender> newSelection) {
setState(() {
// 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;
2024-05-05 01:02:09 +02:00
});
}
@override
Widget build(BuildContext context) {
if (languagesList.isEmpty) {
return const Center(child: CircularProgressIndicator());
} else {
return Scaffold(
appBar: AppBar(
title: const Text("User Data"),
centerTitle: true,
),
body: ListView(
children: [
const Text(
'Location',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
2024-05-09 21:55:17 +02:00
Padding(
padding: const EdgeInsets.all(16.0),
child: LocationSelector(
onLocationChanged: (location) {
setState(() {
_selectedLocation = location;
});
},
),
2024-05-05 01:02:09 +02:00
),
2024-05-09 21:55:17 +02:00
// TODO Show and handle selected location
if (_selectedLocation != null) ...[
Text(style: const TextStyle(backgroundColor: Colors.yellow),
'Selected Location: ${_selectedLocation!.toString()}'),
Text(style: const TextStyle(backgroundColor: Colors.yellowAccent),
'Coordinates: ${_selectedLocation!.toStringDegree()}'),
],
2024-05-05 01:02:09 +02:00
const Text(
2024-05-08 00:59:16 +02:00
'Age',
2024-05-05 01:02:09 +02:00
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
2024-05-08 00:59:16 +02:00
Row(
children: [
const Padding(padding: EdgeInsets.symmetric(horizontal: 8)),
Text(_selectedYear != null
? '${DateTime.now().year - (_selectedYear ?? 0)} years old'
: 'undefined'),
const SizedBox(width: 20),
DropdownMenu(
onSelected: (int? newValue) {
setState(() {
_selectedYear = newValue;
});
},
dropdownMenuEntries: List.generate(50, (index) {
return DropdownMenuEntry<int>(
value: DateTime.now().year - 16 - index,
label: '${DateTime.now().year - 16 - index}',
);
}),
label: const Text('birth year'),
initialSelection: _yearFromDb,
),
],
2024-05-05 01:02:09 +02:00
),
2024-05-06 15:21:21 +02:00
Text(
'Gender (${genderView.name} selected)',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
2024-05-05 01:02:09 +02:00
),
2024-05-06 15:21:21 +02:00
SegmentedButton<Gender>(
style: SegmentedButton.styleFrom(
selectedBackgroundColor: Colors.blue,
),
segments: const <ButtonSegment<Gender>>[
ButtonSegment<Gender>(
value: Gender.none,
label: Text('none'),
),
ButtonSegment<Gender>(
value: Gender.male,
label: Text('male'),
//icon: Icon(Icons.male_sharp),
),
ButtonSegment<Gender>(
value: Gender.female,
label: Text('female'),
//icon: Icon(Icons.female_sharp),
),
ButtonSegment<Gender>(
value: Gender.divers,
label: Text('divers'),
),
],
selected: <Gender>{genderView},
showSelectedIcon: false,
onSelectionChanged: updateSelectedGender,
2024-05-05 01:02:09 +02:00
),
const Divider(),
Text(
'Language: (${_selectedLanguages.length} selected)',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
...languagesList.map(buildSingleCheckbox), // ... spread operator
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: MyButton(text: "Save", onTap: () => saveUserData(context)),
)
],
),
);
}
}
Widget buildSingleCheckbox(LanguageSetting languageSetting) => buildCheckbox(
languageSetting: languageSetting,
onClicked: () {
setState(() {
final newValue = !languageSetting.isSelected;
languageSetting.isSelected = newValue;
if (languageSetting.isSelected &&
!_selectedLanguages.contains(languageSetting.language)) {
_selectedLanguages.add(languageSetting.language);
} else if (languageSetting.isSelected == false) {
_selectedLanguages.removeWhere(
(element) => element.code == languageSetting.language.code);
}
});
},
);
Widget buildCheckbox({
required LanguageSetting languageSetting,
required VoidCallback onClicked,
}) =>
ListTile(
2024-05-06 15:21:21 +02:00
tileColor: Theme.of(context).colorScheme.secondary,
2024-05-05 01:02:09 +02:00
onTap: onClicked,
leading: SizedBox(
width: 48,
height: 32,
child: SvgPicture.asset(
languageSetting.language.iconFile,
),
),
title: Text(
languageSetting.language.nativeName,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
subtitle: Text(languageSetting.language.name),
trailing: Checkbox(
value: languageSetting.isSelected,
onChanged: (value) => onClicked(),
),
);
}