Skip to content

Commit

Permalink
Merge pull request #27 from AdrianMargineanu/edit_profile
Browse files Browse the repository at this point in the history
Edit profile
  • Loading branch information
IoanaAlexandru authored Sep 18, 2020
2 parents 4a35268 + 48d3718 commit fa1b0e9
Show file tree
Hide file tree
Showing 23 changed files with 656 additions and 288 deletions.
15 changes: 3 additions & 12 deletions lib/authentication/model/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,16 @@ class User {
String firstName;
String lastName;

String degree;
String domain;
String year;
String series;
String group;
String subgroup;
/// Info about the user's assigned group (including degree, year of study, series etc)
List<String> classes;

int permissionLevel;

User(
{@required this.uid,
@required this.firstName,
@required this.lastName,
this.degree,
this.domain,
this.year,
this.series,
this.group,
this.subgroup,
this.classes,
int permissionLevel})
: this.permissionLevel = permissionLevel ?? 0;

Expand Down
138 changes: 72 additions & 66 deletions lib/authentication/service/auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,28 @@ import 'dart:async';

import 'package:acs_upb_mobile/authentication/model/user.dart';
import 'package:acs_upb_mobile/generated/l10n.dart';
import 'package:acs_upb_mobile/pages/filter/model/filter.dart';
import 'package:acs_upb_mobile/pages/filter/service/filter_provider.dart';
import 'package:acs_upb_mobile/resources/locale_provider.dart';
import 'package:acs_upb_mobile/resources/validator.dart';
import 'package:acs_upb_mobile/widgets/toast.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

extension MapUtils<K, V> on Map<K, V> {
V getIfPresent(K key) {
if (this.containsKey(key)) {
return this[key];
} else {
return null;
}
}
}

extension DatabaseUser on User {
static User fromSnap(DocumentSnapshot snap) {
String degree;
String domain;
String year;
String series;
String group;
String subgroup;
if (snap.data.containsKey('class')) {
degree = snap.data['class']['degree'];
domain = snap.data['class']['domain'];
year = snap.data['class']['year'];
series = snap.data['class']['series'];
group = snap.data['class']['group'];
subgroup = snap.data['class']['subgroup'];
}

return User(
uid: snap.documentID,
firstName: snap.data['name']['first'],
lastName: snap.data['name']['last'],
degree: degree,
domain: domain,
year: year,
series: series,
group: group,
subgroup: subgroup,
classes: List.from(snap.data['class'] ?? []),
permissionLevel: snap.data['permissionLevel']);
}

Map<String, dynamic> toData() {
Map<String, String> classInfo = {};
if (degree != null) classInfo['degree'] = degree;
if (domain != null) classInfo['domain'] = domain;
if (year != null) classInfo['year'] = year;
if (series != null) classInfo['series'] = series;
if (group != null) classInfo['group'] = group;
if (subgroup != null) classInfo['subgroup'] = subgroup;

return {
'name': {'first': firstName, 'last': lastName},
'class': classInfo,
'class': classes,
'permissionLevel': permissionLevel
};
}
Expand Down Expand Up @@ -184,6 +144,33 @@ class AuthProvider with ChangeNotifier {
return _firebaseUser.uid;
}

bool isOldFormat(Map<String, dynamic> userData) =>
userData['class'] != null && userData['class'] is Map;

/// Change the `class` of the user data in Firebase to the new format.
///
/// The old format of class in the database is a `Map<String, String>`,
/// where the key is the name of the level in the filter tree.
/// In the new format, the class is simply a `List<String>` that contains the
/// name of the nodes.
Future<void> migrateToNewClassFormat(Map<String, dynamic> userData) async {
List<String> classes = [
'degree',
'domain',
'year',
'series',
'group',
'subgroup'
].map((key) => userData['class'][key]).where((s) => s != null).toList();

userData['class'] = classes;

await Firestore.instance
.collection('users')
.document(_firebaseUser.uid)
.updateData(userData);
}

Future<User> _fetchUser() async {
if (isAnonymous) {
return null;
Expand All @@ -192,6 +179,10 @@ class AuthProvider with ChangeNotifier {
.collection('users')
.document(_firebaseUser.uid)
.get();
if (snapshot.data == null) return null;

if (isOldFormat(snapshot.data))
await migrateToNewClassFormat(snapshot.data);

_currentUser = DatabaseUser.fromSnap(snapshot);
return _currentUser;
Expand Down Expand Up @@ -326,28 +317,15 @@ class AuthProvider with ChangeNotifier {
}

/// Create a new user with the data in [info].
Future<bool> signUp({Map<String, String> info, BuildContext context}) async {
Future<bool> signUp({Map<String, dynamic> info, BuildContext context}) async {
try {
String email = info[S.of(context).labelEmail];
String password = info[S.of(context).labelPassword];
String confirmPassword = info[S.of(context).labelConfirmPassword];
String firstName = info[S.of(context).labelFirstName];
String lastName = info[S.of(context).labelLastName];

Filter filter =
Provider.of<FilterProvider>(context, listen: false).cachedFilter;
String degree = info.getIfPresent(
filter.localizedLevelNames[0][LocaleProvider.localeString]);
String domain = info.getIfPresent(
filter.localizedLevelNames[1][LocaleProvider.localeString]);
String year = info.getIfPresent(
filter.localizedLevelNames[2][LocaleProvider.localeString]);
String series = info.getIfPresent(
filter.localizedLevelNames[3][LocaleProvider.localeString]);
String group = info.getIfPresent(
filter.localizedLevelNames[4][LocaleProvider.localeString]);
String subgroup = info.getIfPresent(
filter.localizedLevelNames[5][LocaleProvider.localeString]);
List<String> classes = info['class'] ?? null;

if (email == null || email == '') {
AppToast.show(S.of(context).errorInvalidEmail);
Expand Down Expand Up @@ -388,15 +366,11 @@ class AuthProvider with ChangeNotifier {

// Create document in 'users'
var user = User(
uid: res.user.uid,
firstName: firstName,
lastName: lastName,
degree: degree,
domain: domain,
year: year,
series: series,
group: group,
subgroup: subgroup);
uid: res.user.uid,
firstName: firstName,
lastName: lastName,
classes: classes,
);

DocumentReference ref =
Firestore.instance.collection('users').document(user.uid);
Expand Down Expand Up @@ -435,4 +409,36 @@ class AuthProvider with ChangeNotifier {
}
return true;
}

/// Update the user information with the data in [info].
Future<bool> updateProfile(
{Map<String, dynamic> info, BuildContext context}) async {
try {
String firstName = info[S.of(context).labelFirstName];
String lastName = info[S.of(context).labelLastName];

List<String> classes = info['class'] ?? null;

User user =
await Provider.of<AuthProvider>(context, listen: false).currentUser;
user.firstName = firstName;
user.lastName = lastName;
user.classes = classes;

Firestore.instance
.collection('users')
.document(user.uid)
.updateData(user.toData());

var userUpdateInfo = UserUpdateInfo();
userUpdateInfo.displayName = firstName + ' ' + lastName;
await _firebaseUser.updateProfile(userUpdateInfo);

notifyListeners();
return true;
} catch (e) {
_errorHandler(e, context);
return false;
}
}
}
103 changes: 103 additions & 0 deletions lib/authentication/view/dropdown_tree.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'package:acs_upb_mobile/pages/filter/model/filter.dart';
import 'package:acs_upb_mobile/pages/filter/service/filter_provider.dart';
import 'package:acs_upb_mobile/resources/locale_provider.dart';
import 'package:acs_upb_mobile/widgets/form/form.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class DropdownTreeController {
_DropdownTreeState _dropdownTreeState;

List<String> get path =>
_dropdownTreeState.nodes.map((e) => e.name).skip(1).toList();
}

class DropdownTree extends StatefulWidget {
final List<String> initialPath;
final double leftPadding;
final DropdownTreeController controller;

DropdownTree({
Key key,
this.initialPath,
this.leftPadding,
this.controller,
}) : super(key: key);

@override
_DropdownTreeState createState() => _DropdownTreeState();
}

class _DropdownTreeState extends State<DropdownTree> {
List<FormItem> formItems;
FilterProvider filterProvider;
Filter filter;
List<FilterNode> nodes;

List<Widget> _buildDropdowns(BuildContext context) {
List<Widget> items = [SizedBox(height: 8)];
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] != null && nodes[i].children.isNotEmpty) {
items.add(Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding:
EdgeInsets.only(top: 8.0, left: widget.leftPadding ?? 0.0),
child: Text(
filter.localizedLevelNames[i][LocaleProvider.localeString],
style: Theme.of(context)
.textTheme
.caption
.apply(color: Theme.of(context).hintColor),
),
),
DropdownButtonFormField<FilterNode>(
value: nodes.length > i + 1 ? nodes[i + 1] : null,
items: nodes[i]
.children
.map((node) => DropdownMenuItem(
value: node,
child: Padding(
padding: EdgeInsets.only(
top: 8.0, left: widget.leftPadding ?? 0.0),
child: Text(node.name),
),
))
.toList(),
onChanged: (selected) => setState(
() {
nodes.removeRange(i + 1, nodes.length);
nodes.add(selected);
},
),
),
],
));
}
}
return items;
}

@override
Widget build(BuildContext context) {
widget.controller?._dropdownTreeState = this;

return FutureBuilder(
future: Provider.of<FilterProvider>(context).fetchFilter(context),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
filter = snap.data;
nodes ??= widget.initialPath == null
? [filter.root]
: filter.findNodesByPath(widget.initialPath);
return Column(
children: _buildDropdowns(context),
);
}
return Center(child: CircularProgressIndicator());
},
);
}
}
Loading

0 comments on commit fa1b0e9

Please sign in to comment.