First step of registration process

master
Rafael 2024-05-17 23:08:26 +02:00
parent bf20cbbd1f
commit 1cb7970982
6 changed files with 398 additions and 263 deletions

View File

@ -104,7 +104,7 @@ class MyDrawer extends StatelessWidget {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const UserDataPage(), builder: (context) => const UserDataPage(isRegProcess: false,),
)); ));
}, },
), ),

View File

@ -1,3 +1,10 @@
enum Gender {
none,
male,
female,
divers,
}
enum SkillOption { enum SkillOption {
product, product,
finance, finance,

View File

@ -3,6 +3,7 @@ import '../../helper.dart';
class ProfileCategoryForm<T> extends StatefulWidget { class ProfileCategoryForm<T> extends StatefulWidget {
final String title; final String title;
final String header;
final String description; final String description;
final List<T> options; // T to make it work with our different Enums final List<T> options; // T to make it work with our different Enums
final int minSelections; final int minSelections;
@ -15,6 +16,7 @@ class ProfileCategoryForm<T> extends StatefulWidget {
const ProfileCategoryForm({ const ProfileCategoryForm({
super.key, super.key,
required this.title, required this.title,
required this.header,
required this.description, required this.description,
required this.options, required this.options,
required this.minSelections, required this.minSelections,
@ -51,17 +53,15 @@ class ProfileCategoryFormState<T> extends State<ProfileCategoryForm<T>> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text( Text(
'${widget.title}', widget.header,
style: const TextStyle( style: const TextStyle(
fontSize: 18.0, fontSize: 18.0,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 16,), const SizedBox(height: 16),
Text( Text(widget.description),
'${widget.description}', const SizedBox(height: 16),
),
const SizedBox(height: 16,),
Wrap( Wrap(
spacing: 8.0, spacing: 8.0,
children: List.generate(widget.options.length, (index) { children: List.generate(widget.options.length, (index) {

View File

@ -6,12 +6,17 @@ import '../../services/auth/auth_service.dart';
import 'profile_category_form.dart'; import 'profile_category_form.dart';
class SkillsForm extends StatelessWidget { class SkillsForm extends StatelessWidget {
SkillsForm({super.key, required this.skillsSought}); SkillsForm({
super.key,
required this.isRegProcess,
required this.skillsSought,
});
// get instance of firestore and auth // get instance of firestore and auth
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
final bool isRegProcess;
final bool skillsSought; // flag to toggle offered and sought skills final bool skillsSought; // flag to toggle offered and sought skills
@override @override
@ -28,18 +33,42 @@ class SkillsForm extends StatelessWidget {
List<SkillOption>? userSkills = snapshot.data; List<SkillOption>? userSkills = snapshot.data;
return ProfileCategoryForm( return ProfileCategoryForm(
title: 'Skills', title: 'Skills',
header:
skillsSought ? 'Skills you are looking for' : 'Your own skills',
description: skillsSought description: skillsSought
? 'Choose up to 3 areas you are looking for in a cofounder' ? 'Choose up to 3 areas you are looking for in a co-founder'
: 'Select up to 3 areas in which you are skilled', : 'Select up to 3 areas in which you are skilled',
saveButtonText: isRegProcess ? 'Save and continue' : 'Save',
options: SkillOption.values.toList(), // Convert enum values to list options: SkillOption.values.toList(), // Convert enum values to list
minSelections: 1, minSelections: 1,
maxSelections: 3, maxSelections: 3,
preSelectedOptions: preSelectedOptions:
userSkills ?? [], // Pass pre-selected skills to the form userSkills ?? [], // Pass pre-selected skills to the form
onSave: (selectedOptions) { onSave: (selectedOptions) async {
// Handle saving selected options // Handle saving selected options
saveSkillsToFirebase(selectedOptions.cast<SkillOption>()); bool success = await saveSkillsToFirebase(
selectedOptions.cast<SkillOption>());
// Then navigate to another screen or perform any other action??? // Then navigate to another screen or perform any other action???
if (context.mounted) {
if (success) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SkillsForm(
isRegProcess: isRegProcess,
skillsSought: true,
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to save user skills.'),
),
);
}
}
}, },
); );
} }
@ -80,28 +109,34 @@ class SkillsForm extends StatelessWidget {
return []; return [];
} }
void saveSkillsToFirebase(List<SkillOption> selectedOptions) { Future<bool> saveSkillsToFirebase(List<SkillOption> selectedOptions) async {
String currentUserId = _authService.getCurrentUser()!.uid; try {
String currentUserId = _authService.getCurrentUser()!.uid;
// Convert enum values to strings, removing leading EnumType with split // Convert enum values to strings, removing leading EnumType with split
List<String> skills = selectedOptions List<String> skills = selectedOptions
.map((option) => option.toString().split('.').last) .map((option) => option.toString().split('.').last)
.toList(); .toList();
// Update the corresponding 'skills' field in the user's document // Update the corresponding 'skills' field in the user's document
String keyToUpdate = skillsSought String keyToUpdate = skillsSought
? Constants.dbFieldUsersSkillsSought ? Constants.dbFieldUsersSkillsSought
: Constants.dbFieldUsersSkills; : Constants.dbFieldUsersSkills;
_firestore _firestore
.collection(Constants.dbCollectionUsers) .collection(Constants.dbCollectionUsers)
.doc(currentUserId) .doc(currentUserId)
.update({ .update({
keyToUpdate: skills, keyToUpdate: skills,
}).then((_) { }).then((_) {
print('$keyToUpdate saved to Firebase: $skills'); print('$keyToUpdate saved to Firebase: $skills');
}).catchError((error) { }).catchError((error) {
print('Failed to save $keyToUpdate: $error'); print('Failed to save $keyToUpdate: $error');
}); });
return true;
} catch (e) {
return false;
}
} }
} }

View File

@ -1,25 +1,28 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import '../components/my_button.dart';
import '../constants.dart';
import '../models/language.dart';
import '../models/language_setting.dart';
import '../components/location_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../services/auth/auth_service.dart'; import '../components/location_dialog.dart';
import '../components/my_button.dart';
import '../components/my_drawer.dart';
import '../constants.dart';
import '../enumerations.dart';
import '../forms/skills_form.dart';
import '../models/language.dart';
import '../models/language_setting.dart';
import '../models/location.dart'; import '../models/location.dart';
import '../services/auth/auth_service.dart';
class UserDataPage extends StatefulWidget { class UserDataPage extends StatefulWidget {
const UserDataPage({super.key}); final bool isRegProcess;
const UserDataPage({super.key, required this.isRegProcess});
@override @override
State<UserDataPage> createState() => _UserDataPageState(); State<UserDataPage> createState() => _UserDataPageState();
} }
enum Gender { none, male, female, divers }
class _UserDataPageState extends State<UserDataPage> { class _UserDataPageState extends State<UserDataPage> {
MyLocation? _mainLocation; MyLocation? _mainLocation;
MyLocation? _secondaryLocation; MyLocation? _secondaryLocation;
@ -129,7 +132,7 @@ class _UserDataPageState extends State<UserDataPage> {
// Load data from JSON file when the widget initializes // Load data from JSON file when the widget initializes
loadLanguagesFromJson(); loadLanguagesFromJson();
} catch (error) { } catch (error) {
print("Error fetching settings: $error"); _showSnackBar("Error fetching settings: $error");
} }
} }
@ -164,103 +167,99 @@ class _UserDataPageState extends State<UserDataPage> {
}); });
} }
void saveUserData(BuildContext context) async { Future<bool> saveUserData(BuildContext context) async {
// Get userID from auth service try {
String currentUserID = _authService.getCurrentUser()!.uid; // Get userID from auth service
String currentUserID = _authService.getCurrentUser()!.uid;
// Get references to the current users Firebase collections // Get references to the current users Firebase collections
DocumentReference userRef = DocumentReference userRef =
_firestore.collection(Constants.dbCollectionUsers).doc(currentUserID); _firestore.collection(Constants.dbCollectionUsers).doc(currentUserID);
CollectionReference languagesRef = CollectionReference languagesRef =
userRef.collection(Constants.dbCollectionLanguages); userRef.collection(Constants.dbCollectionLanguages);
CollectionReference locationsRef = CollectionReference locationsRef =
userRef.collection(Constants.dbCollectionLocations); userRef.collection(Constants.dbCollectionLocations);
if (_selectedYear != _yearFromDb) { if (_selectedYear != _yearFromDb) {
await userRef.update( await userRef.update(
{'born': _selectedYear}, {'born': _selectedYear},
); );
// update local value // update local value
_yearFromDb = _selectedYear; _yearFromDb = _selectedYear;
} else {
print("birth year did NOT change");
}
// 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 locations
if (_mainLocation != _mainLocationFromDb) {
if (_mainLocation != null) {
// Update existing user document with new locations
await locationsRef
.doc(Constants.dbDocMainLocation)
.set(_mainLocation!.toMap());
} }
} else {
print("main location did NOT change");
}
if (_secondaryLocation != _secondaryLocationFromDb) { // Update Gender in database - only if value has changed
if (_secondaryLocation != null) { if (_genderFromDb != genderView.index) {
await locationsRef await userRef.update(
.doc(Constants.dbDocSecondLocation) {'gender': genderView.index},
.set(_secondaryLocation!.toMap()) );
.then((value) => { // update local value
print("Document secondary location updated"), _genderFromDb = genderView.index;
}
// Save locations
if (_mainLocation != _mainLocationFromDb) {
if (_mainLocation != null) {
// Update existing user document with new locations
await locationsRef
.doc(Constants.dbDocMainLocation)
.set(_mainLocation!.toMap());
}
}
if (_secondaryLocation != _secondaryLocationFromDb) {
if (_secondaryLocation != null) {
await locationsRef
.doc(Constants.dbDocSecondLocation)
.set(_secondaryLocation!.toMap())
.then((value) => {
// update local values
_secondaryLocationFromDb = _secondaryLocation,
_secondLocationExists = true
});
} else if (_secondLocationExists) {
// secondLocationExists but is null here -> delete secondary in DB
await locationsRef.doc(Constants.dbDocSecondLocation).delete().then(
(doc) => {
// update local values // update local values
_secondaryLocationFromDb = _secondaryLocation, _secondaryLocationFromDb = null,
_secondLocationExists = true _secondLocationExists = false
}); },
} else if (_secondLocationExists) { onError: (e) => _showSnackBar("Error updating document: $e"),
// secondLocationExists but is null here -> delete secondary in DB );
await locationsRef.doc(Constants.dbDocSecondLocation).delete().then( }
(doc) => {
print("Document secondary location deleted"),
// update local values
_secondaryLocationFromDb = null,
_secondLocationExists = false
},
onError: (e) => print("Error updating document $e"),
);
} }
} else {
print("secondary location did NOT change");
}
// Save Languages - only if selected values changed // Save Languages - only if selected values changed
if (_selectedLanguages.isEmpty) { if (_selectedLanguages.isEmpty) {
print("no language selected"); _showSnackBar("No language selected");
} else if (_languagesFromDb.length != _selectedLanguages.length || return false;
_selectedLanguages } else if (_languagesFromDb.length != _selectedLanguages.length ||
.any((element) => !_languagesFromDb.contains(element))) { _selectedLanguages
// Loop through each Language object and save it to the collection .any((element) => !_languagesFromDb.contains(element))) {
for (int i = 0; i < _selectedLanguages.length; i++) { // Loop through each Language object and save it to the collection
// Convert Language object to a map and add the map to the collection, for (int i = 0; i < _selectedLanguages.length; i++) {
// using .doc(myID).set() instead of .add() to avoid duplicates. // Convert Language object to a map and add the map to the collection,
await languagesRef // using .doc(myID).set() instead of .add() to avoid duplicates.
.doc(_selectedLanguages[i].code) await languagesRef
.set(_selectedLanguages[i].toMap()); .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);
} }
// update local variable (only if selection is not empty)
_languagesFromDb.clear();
_languagesFromDb.addAll(_selectedLanguages);
// List to store language codes from the provided list return true;
List<String> languageCodes = } catch (e) {
_selectedLanguages.map((language) => language.code).toList(); _showSnackBar(e.toString());
// Clean up languages that were not part of the provided list return false;
await deleteUnusedDocuments(languagesRef, languageCodes);
} else {
print("languages did NOT change");
} }
} }
@ -326,141 +325,185 @@ class _UserDataPageState extends State<UserDataPage> {
title: const Text("User Data"), title: const Text("User Data"),
centerTitle: true, centerTitle: true,
), ),
body: ListView( drawer: const MyDrawer(),
children: [ body: Padding(
const Text( padding: const EdgeInsets.all(16.0),
'Location', child: ListView(
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), children: [
), if (widget.isRegProcess) ...[
const SizedBox(height: 20), const SizedBox(height: 10),
// Display selected main location const Text(
const Text( 'Please fill in the following fields to proceed with the registration process.',
'Main Location:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: TextStyle(fontWeight: FontWeight.bold), ),
), const SizedBox(height: 20),
if (_mainLocation != null) ...[ const Divider(),
Text(_mainLocation!.toString()), ],
const SizedBox(height: 10), const SizedBox(height: 10),
] else ...[
const Text('n/a'),
const SizedBox(height: 10),
],
// Button to set main location
Center(
child: ElevatedButton(
onPressed: () {
_showLocationDialog(true);
},
child: Text(_mainLocation != null
? 'Change Main Location'
: 'Set Main Location'),
),
),
const SizedBox(height: 20),
// Display selected secondary location
if (_secondaryLocation != null) ...[
const Text( const Text(
'Secondary Location:', 'Location',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
// Display selected main location
const Text(
'Main Location:',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
Text(_secondaryLocation!.toString()), if (_mainLocation != null) ...[
const SizedBox(height: 10), Text(_mainLocation!.toString()),
], const SizedBox(height: 10),
if (_mainLocation != null) ...[ ] else ...[
// Button to set secondary location const Text('n/a'),
ElevatedButton( const SizedBox(height: 10),
onPressed: () { ],
_showLocationDialog(false); // Button to set main location
}, Center(
child: Text(_secondaryLocation != null child: ElevatedButton(
? 'Change Secondary Location' onPressed: () {
: 'Add Secondary Location'), _showLocationDialog(true);
),
],
// Display selected secondary location or remove button
if (_secondaryLocation != null) ...[
ElevatedButton(
onPressed: _removeSecondaryLocation,
child: const Text('Remove Secondary Location'),
),
],
const SizedBox(height: 20),
const Text(
'Age',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
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(
initialSelection: _selectedYear,
onSelected: (int? newValue) {
setState(() {
_selectedYear = newValue;
});
}, },
dropdownMenuEntries: List.generate(50, (index) { child: Text(_mainLocation != null
return DropdownMenuEntry<int>( ? 'Change Main Location'
value: DateTime.now().year - 16 - index, : 'Set Main Location'),
label: '${DateTime.now().year - 16 - index}', ),
); ),
}), const SizedBox(height: 20),
label: const Text('birth year'), // Display selected secondary location
if (_secondaryLocation != null) ...[
const Text(
'Secondary Location:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(_secondaryLocation!.toString()),
const SizedBox(height: 10),
],
if (_mainLocation != null) ...[
// Button to set secondary location
Center(
child: ElevatedButton(
onPressed: () {
_showLocationDialog(false);
},
child: Text(_secondaryLocation != null
? 'Change Secondary Location'
: 'Add Secondary Location'),
),
), ),
], ],
), // Display selected secondary location or remove button
const SizedBox(height: 20), if (_secondaryLocation != null) ...[
Text( Center(
'Gender (${genderView.name} selected)', child: ElevatedButton(
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), onPressed: _removeSecondaryLocation,
), child: const Text('Remove Secondary Location'),
Center( ),
child: SegmentedButton<Gender>(
style: SegmentedButton.styleFrom(
selectedBackgroundColor: Colors.blue,
), ),
segments: const <ButtonSegment<Gender>>[ ],
ButtonSegment<Gender>( const SizedBox(height: 20),
value: Gender.none, const Text(
label: Text('none'), 'Age',
), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
ButtonSegment<Gender>( ),
value: Gender.male, Row(
label: Text('male'), children: [
//icon: Icon(Icons.male_sharp), const Padding(padding: EdgeInsets.symmetric(horizontal: 8)),
), Text(_selectedYear != null
ButtonSegment<Gender>( ? '${DateTime.now().year - (_selectedYear ?? 0)} years old'
value: Gender.female, : 'undefined age'),
label: Text('female'), const SizedBox(width: 20),
//icon: Icon(Icons.female_sharp), DropdownMenu(
), initialSelection: _selectedYear,
ButtonSegment<Gender>( onSelected: (int? newValue) {
value: Gender.divers, setState(() {
label: Text('divers'), _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'),
), ),
], ],
selected: <Gender>{genderView},
showSelectedIcon: false,
onSelectionChanged: updateSelectedGender,
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20), Text(
const Divider(), 'Gender (${genderView.name} selected)',
Text( style:
'Language: (${_selectedLanguages.length} selected)', const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ),
), Center(
...languagesList.map(buildSingleCheckbox), // ... spread operator child: SegmentedButton<Gender>(
const Divider(), style: SegmentedButton.styleFrom(
Padding( selectedBackgroundColor: Colors.blue,
padding: const EdgeInsets.all(8.0), ),
child: MyButton(text: "Save", onTap: () => saveUserData(context)), 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,
),
),
const SizedBox(height: 20),
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: widget.isRegProcess ? 'Save and continue' : "Save",
onTap: () async {
bool success = await saveUserData(context);
if (context.mounted) {
if (success) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SkillsForm(
isRegProcess: widget.isRegProcess,
skillsSought: false,
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to save user data.'),
),
);
}
}
},
),
),
],
),
), ),
); );
} }
@ -508,4 +551,19 @@ class _UserDataPageState extends State<UserDataPage> {
onChanged: (value) => onClicked(), onChanged: (value) => onClicked(),
), ),
); );
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
}
} }

View File

@ -1,9 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cofounderella/services/auth/login_or_register.dart'; import 'auth_service.dart';
import 'package:cofounderella/pages/home_page.dart'; import 'login_or_register.dart';
import '../../constants.dart';
import '../../pages/home_page.dart';
import '../../pages/user_data_page.dart';
class AuthGate extends StatelessWidget{ class AuthGate extends StatelessWidget {
const AuthGate({super.key}); const AuthGate({super.key});
@override @override
@ -11,17 +15,48 @@ class AuthGate extends StatelessWidget{
return Scaffold( return Scaffold(
body: StreamBuilder( body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(), stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot){ builder: (context, snapshot) {
// check if user is logged in or not // check if user is logged in or not
if(snapshot.hasData){ if (snapshot.hasData) {
return HomePage(); return FutureBuilder(
} // check database entries, if data is missing
else { // then complete registration process
// else go to HomePage
future: _checkCollectionsExist(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasData && snapshot.data == true) {
return HomePage();
} else {
// also in case (snapshot.hasError)
return const UserDataPage(isRegProcess: true);
}
},
);
} else {
return const LoginOrRegister(); return const LoginOrRegister();
} }
}, },
), ),
); );
} }
Future<bool> _checkCollectionsExist() async {
bool languagesExist =
await _checkUsersCollectionExists(Constants.dbCollectionLanguages);
bool locationsExist =
await _checkUsersCollectionExists(Constants.dbCollectionLocations);
return languagesExist && locationsExist;
}
Future<bool> _checkUsersCollectionExists(String collectionName) async {
String currentUserId = AuthService().getCurrentUser()!.uid;
final collection = FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(currentUserId)
.collection(collectionName);
final snapshot = await collection.limit(1).get();
return snapshot.docs.isNotEmpty;
}
} }