Added SkillsForm

master
Rafael 2024-05-15 13:35:01 +02:00
parent 3fa52d3e85
commit 4e2ced6802
8 changed files with 311 additions and 6 deletions

View File

@ -15,6 +15,8 @@ class Constants {
static const String dbFieldUsersGender = 'gender'; static const String dbFieldUsersGender = 'gender';
static const String dbFieldUsersYearBorn = 'born'; static const String dbFieldUsersYearBorn = 'born';
static const String dbFieldUsersSkills = 'skills';
static const String dbFieldUsersSkillsSought = 'skills_sought';
static const String pathLanguagesJson = 'lib/assets/languages.json'; static const String pathLanguagesJson = 'lib/assets/languages.json';
} }

View File

@ -0,0 +1,31 @@
enum SkillOption {
product,
finance,
engineering,
design,
marketing,
management,
operations,
legal;
String get displayName {
switch (this) {
case SkillOption.product:
return 'Product Development';
case SkillOption.design:
return 'Design';
case SkillOption.engineering:
return 'Engineering';
case SkillOption.marketing:
return 'Sales and Marketing';
case SkillOption.finance:
return 'Finance';
case SkillOption.management:
return 'Management';
case SkillOption.operations:
return 'Operations';
case SkillOption.legal:
return 'Legal';
}
}
}

View File

@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import '../../helper.dart';
class ProfileCategoryForm<T> extends StatefulWidget {
final String title;
final String description;
final List<T> options; // T to make it work with our different Enums
final int minSelections;
final int maxSelections;
final Function(List<T>) onSave;
final List<T> preSelectedOptions;
final String? saveButtonText;
final bool hideSaveButton;
const ProfileCategoryForm({
super.key,
required this.title,
required this.description,
required this.options,
required this.minSelections,
required this.maxSelections,
required this.onSave,
required this.preSelectedOptions,
this.saveButtonText,
this.hideSaveButton = false, // Initialize hideSaveButton parameter
});
@override
ProfileCategoryFormState<T> createState() => ProfileCategoryFormState<T>();
}
class ProfileCategoryFormState<T> extends State<ProfileCategoryForm<T>> {
late List<T> _selectedOptions = [];
bool _isSnackBarVisible = false; // Track SnackBar visibility
@override
void initState() {
super.initState();
_selectedOptions = List.from(widget.preSelectedOptions);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'${widget.title}',
style: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16,),
Text(
'${widget.description}',
),
const SizedBox(height: 16,),
Wrap(
spacing: 8.0,
children: List.generate(widget.options.length, (index) {
final option = widget.options[index];
return ChoiceChip(
label: Text(_getDisplayText(option)),
selected: _selectedOptions.contains(option),
onSelected: (selected) {
setState(() {
if (selected) {
if (_selectedOptions.length < widget.maxSelections) {
_selectedOptions.add(option);
} else {
// Provide feedback that maximum selections reached
_showSnackBar(
'Maximum selections reached for ${widget.title}');
}
} else {
_selectedOptions.remove(option);
}
});
},
);
}),
),
const SizedBox(height: 16.0),
if (!widget.hideSaveButton)
ElevatedButton(
onPressed: () {
if (!equalContent(
_selectedOptions, widget.preSelectedOptions)) {
// Only save if options have changed
if (_selectedOptions.length >= widget.minSelections) {
widget.onSave(_selectedOptions);
// TODO Navigation abhängig von caller
// Navigator.pop(context); // Navigate back after saving
} else {
// Provide feedback that minimum selections not met
_showSnackBar('No selection made for ${widget.title}');
}
} else {
print('No changes to save for ${widget.title}.');
}
},
child: Text(widget.saveButtonText ?? 'Save'),
),
],
),
),
);
}
String _getDisplayText(dynamic option) {
// Check if the option is an enum and has a displayName property
if (option is Enum) {
final dynamicEnum = option as dynamic;
if (dynamicEnum.displayName != null) {
return dynamicEnum.displayName;
}
}
// Fallback to default toString if not an enum
return option.toString().split('.').last;
}
void _showSnackBar(String message) {
if (!_isSnackBarVisible) {
_isSnackBarVisible = true;
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
//duration: Duration(seconds: 2),
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
_isSnackBarVisible = false;
},
),
),
)
.closed
.then((value) {
_isSnackBarVisible = false;
});
}
}
}

View File

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../constants.dart';
import '../../enumerations.dart';
import '../../services/auth/auth_service.dart';
import 'profile_category_form.dart';
class SkillsForm extends StatelessWidget {
SkillsForm({super.key, required this.skillsSought});
// get instance of firestore and auth
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final AuthService _authService = AuthService();
final bool skillsSought; // flag to toggle offered and sought skills
@override
Widget build(BuildContext context) {
return FutureBuilder<List<SkillOption>>(
future: getSkillsFromFirebase(), // Fetch skills from Firebase
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Show loading indicator while fetching data
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<SkillOption>? userSkills = snapshot.data;
return ProfileCategoryForm(
title: 'Skills',
description: skillsSought
? 'Choose up to 3 areas you are looking for in a cofounder'
: 'Select up to 3 areas in which you are skilled',
options: SkillOption.values.toList(), // Convert enum values to list
minSelections: 1,
maxSelections: 3,
preSelectedOptions:
userSkills ?? [], // Pass pre-selected skills to the form
onSave: (selectedOptions) {
// Handle saving selected options
saveSkillsToFirebase(selectedOptions.cast<SkillOption>());
// Then navigate to another screen or perform any other action???
},
);
}
},
);
}
Future<List<SkillOption>> getSkillsFromFirebase() async {
// Fetch skills from Firestore
String currentUserId = _authService.getCurrentUser()!.uid;
DocumentSnapshot userDoc = await _firestore
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.get();
if (userDoc.exists && userDoc.data() != null) {
Map<String, dynamic> userData =
userDoc.data()! as Map<String, dynamic>; // Explicit cast
List<dynamic>? skills;
if (skillsSought) {
skills = userData[Constants.dbFieldUsersSkillsSought];
} else {
skills = userData[
Constants.dbFieldUsersSkills]; //as List<dynamic>?; // Explicit cast
}
if (skills != null && skills.isNotEmpty) {
// Convert skills from strings to enum values
List<SkillOption> userSkills = skills
.map((skill) => SkillOption.values
.firstWhere((x) => x.toString() == 'SkillOption.$skill'))
.toList();
return userSkills;
}
}
return [];
}
void saveSkillsToFirebase(List<SkillOption> selectedOptions) {
String currentUserId = _authService.getCurrentUser()!.uid;
// Convert enum values to strings, removing leading EnumType with split
List<String> skills = selectedOptions
.map((option) => option.toString().split('.').last)
.toList();
// Update the corresponding 'skills' field in the user's document
String keyToUpdate = skillsSought
? Constants.dbFieldUsersSkillsSought
: Constants.dbFieldUsersSkills;
_firestore
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.update({
keyToUpdate: skills,
}).then((_) {
print('$keyToUpdate saved to Firebase: $skills');
}).catchError((error) {
print('Failed to save $keyToUpdate: $error');
});
}
}

View File

@ -1,4 +1,15 @@
/// Convert decimal coordinate to degrees minutes seconds (DMS) import 'package:collection/collection.dart';
///
/// Compare two lists by their content ignoring their elements order.
///
bool equalContent(List<dynamic> list1, List<dynamic> list2) {
return const DeepCollectionEquality.unordered().equals(list1, list2);
}
///
/// Convert decimal coordinate to degrees minutes seconds (DMS).
///
String convertDecimalToDMS(double decimalValue) { String convertDecimalToDMS(double decimalValue) {
bool isNegative = decimalValue < 0; bool isNegative = decimalValue < 0;
double absoluteValue = decimalValue.abs(); double absoluteValue = decimalValue.abs();
@ -15,4 +26,4 @@ String convertDecimalToDMS(double decimalValue) {
// return formatted string // return formatted string
return '${degrees.abs()}° ${minutes.abs()}\' ${seconds.abs()}" $direction'; return '${degrees.abs()}° ${minutes.abs()}\' ${seconds.abs()}" $direction';
} }

View File

@ -1,6 +1,6 @@
import 'package:cofounderella/constants.dart'; import '../constants.dart';
import 'package:cofounderella/services/auth/auth_gate.dart'; import '../services/auth/auth_gate.dart';
import 'package:cofounderella/themes/theme_provider.dart'; import '../themes/theme_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@ -74,7 +74,7 @@ packages:
source: hosted source: hosted
version: "3.12.2" version: "3.12.2"
collection: collection:
dependency: transitive dependency: "direct main"
description: description:
name: collection name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a

View File

@ -42,6 +42,7 @@ dependencies:
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
geolocator: ^11.0.0 geolocator: ^11.0.0
geocoding: ^3.0.0 geocoding: ^3.0.0
collection: ^1.18.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: