Skip to content

Commit

Permalink
Add lecturer card in class_view page (#151)
Browse files Browse the repository at this point in the history
* Add teacher bottom sheet card in event view

* Add lecturer info in class_view page

* Add lecturer card test

* Change class_view title

* Bump app version

* Add new release changelogs

* Apply suggestions from code review

Co-authored-by: Ioana Alexandru <[email protected]>

Co-authored-by: Ioana Alexandru <[email protected]>
  • Loading branch information
andreicmirciu and IoanaAlexandru authored Mar 23, 2021
1 parent a0adb21 commit 0337750
Show file tree
Hide file tree
Showing 16 changed files with 241 additions and 64 deletions.
6 changes: 6 additions & 0 deletions android/fastlane/metadata/android/en-GB/changelogs/10004.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fixed
- Error message shown when cancelling profile picture selection

Added
- Card containing the class name and its associated lecturer on the class info page
- Ability to click on a lecturer from an event and display more details about them
6 changes: 6 additions & 0 deletions android/fastlane/metadata/android/en-US/changelogs/10004.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fixed
- Error message shown when cancelling profile picture selection

Added
- Card containing the class name and its associated lecturer on the class info page
- Ability to click on a lecturer from an event and display more details about them
7 changes: 7 additions & 0 deletions android/fastlane/metadata/android/ro/changelogs/10004.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Rezolvat
- Mesaj de eroare afișat la anularea selectării imaginii de profil

Adăugat
- Card care conține numele materiei și cadrul didactic titular pe pagina de informații a unei materii
- Posibilitatea de a apăsa pe un profesor asociat unui eveniment și de a afișa mai multe
detalii despre acesta
1 change: 1 addition & 0 deletions lib/generated/intl/messages_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class MessageLookup extends MessageLookupByLibrary {
"messageWelcomeSimple" : MessageLookupByLibrary.simpleMessage("Welcome!"),
"messageYouCanContribute" : MessageLookupByLibrary.simpleMessage("You can contribute to the app data, but you first need to request permissions."),
"navigationAskPermissions" : MessageLookupByLibrary.simpleMessage("Ask for permissions"),
"navigationClassInfo" : MessageLookupByLibrary.simpleMessage("Class information"),
"navigationClasses" : MessageLookupByLibrary.simpleMessage("Classes"),
"navigationEventDetails" : MessageLookupByLibrary.simpleMessage("Event details"),
"navigationFilter" : MessageLookupByLibrary.simpleMessage("Filter"),
Expand Down
1 change: 1 addition & 0 deletions lib/generated/intl/messages_ro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class MessageLookup extends MessageLookupByLibrary {
"messageWelcomeSimple" : MessageLookupByLibrary.simpleMessage("Bine ai venit!"),
"messageYouCanContribute" : MessageLookupByLibrary.simpleMessage("Poți contribui la datele din aplicație, dar trebuie mai întâi să ceri permisiuni."),
"navigationAskPermissions" : MessageLookupByLibrary.simpleMessage("Cere permisiuni"),
"navigationClassInfo" : MessageLookupByLibrary.simpleMessage("Informații materie"),
"navigationClasses" : MessageLookupByLibrary.simpleMessage("Materii"),
"navigationEventDetails" : MessageLookupByLibrary.simpleMessage("Detalii eveniment"),
"navigationFilter" : MessageLookupByLibrary.simpleMessage("Filtru"),
Expand Down
10 changes: 10 additions & 0 deletions lib/generated/l10n.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
"navigationClasses": "Classes",
"navigationEventDetails": "Event details",
"navigationNewsFeed": "News feed",
"navigationClassInfo": "Class information",

"filterMenuShowAll": "Show all",
"filterMenuShowMine": "Show only mine",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/intl_ro.arb
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
"navigationClasses": "Materii",
"navigationEventDetails": "Detalii eveniment",
"navigationNewsFeed": "Știri",
"navigationClassInfo": "Informații materie",

"filterMenuShowAll": "Arată tot",
"filterMenuShowMine": "Arată doar pe ale mele",
Expand Down
90 changes: 71 additions & 19 deletions lib/pages/classes/view/class_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,21 @@ import 'package:acs_upb_mobile/pages/classes/model/class.dart';
import 'package:acs_upb_mobile/pages/classes/service/class_provider.dart';
import 'package:acs_upb_mobile/pages/classes/view/grading_view.dart';
import 'package:acs_upb_mobile/pages/classes/view/shortcut_view.dart';
import 'package:acs_upb_mobile/pages/people/service/person_provider.dart';
import 'package:acs_upb_mobile/pages/people/view/person_view.dart';
import 'package:acs_upb_mobile/resources/custom_icons.dart';
import 'package:acs_upb_mobile/resources/utils.dart';
import 'package:acs_upb_mobile/widgets/button.dart';
import 'package:acs_upb_mobile/widgets/class_icon.dart';
import 'package:acs_upb_mobile/widgets/dialog.dart';
import 'package:acs_upb_mobile/widgets/icon_text.dart';
import 'package:acs_upb_mobile/widgets/scaffold.dart';
import 'package:acs_upb_mobile/widgets/toast.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:positioned_tap_detector/positioned_tap_detector.dart';
import 'package:provider/provider.dart';

extension ClassExtension on ClassHeader {
Color get colorFromAcronym {
int r = 0, g = 0, b = 0;
if (acronym.isNotEmpty) {
b = acronym[0].codeUnitAt(0);
if (acronym.length >= 2) {
g = acronym[1].codeUnitAt(0);
if (acronym.length >= 3) {
r = acronym[2].codeUnitAt(0);
}
}
}
const int brightnessFactor = 2;
return Color.fromRGBO(
r * brightnessFactor, g * brightnessFactor, b * brightnessFactor, 1);
}
}

class ClassView extends StatefulWidget {
const ClassView({Key key, this.classHeader}) : super(key: key);

Expand All @@ -50,7 +36,7 @@ class _ClassViewState extends State<ClassView> {
final classProvider = Provider.of<ClassProvider>(context);

return AppScaffold(
title: Text(widget.classHeader.name),
title: Text(S.of(context).navigationClassInfo),
body: FutureBuilder(
future: classProvider.fetchClassInfo(widget.classHeader,
context: context),
Expand All @@ -64,6 +50,9 @@ class _ClassViewState extends State<ClassView> {
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
children: [
const SizedBox(height: 8),
lecturerCard(context),
const SizedBox(height: 8),
shortcuts(context),
const SizedBox(height: 8),
GradingChart(
Expand Down Expand Up @@ -241,4 +230,67 @@ class _ClassViewState extends State<ClassView> {
),
);
}

Widget lecturerCard(BuildContext context) {
final personProvider = Provider.of<PersonProvider>(context);

return Card(
key: const Key('LecturerCard'),
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
ClassIcon(classHeader: widget.classHeader),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconText(
icon: Icons.class_,
text: widget.classHeader.name ?? '-',
style: Theme.of(context).textTheme.bodyText1,
),
FutureBuilder(
future: personProvider
.mostRecentLecturer(widget.classHeader.id),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final lecturerName = snapshot.data;
return GestureDetector(
onTap: () async {
final lecturer =
await personProvider.fetchPerson(lecturerName);
if (lecturer != null) {
await showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (BuildContext buildContext) =>
PersonView(person: lecturer));
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconText(
icon: Icons.person,
text: lecturerName ?? '-',
style: Theme.of(context).textTheme.bodyText1,
),
],
),
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
],
),
),
],
),
),
);
}
}
29 changes: 4 additions & 25 deletions lib/pages/classes/view/classes_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import 'package:acs_upb_mobile/generated/l10n.dart';
import 'package:acs_upb_mobile/pages/classes/model/class.dart';
import 'package:acs_upb_mobile/pages/classes/service/class_provider.dart';
import 'package:acs_upb_mobile/pages/classes/view/class_view.dart';
import 'package:acs_upb_mobile/widgets/class_icon.dart';
import 'package:acs_upb_mobile/widgets/error_page.dart';
import 'package:acs_upb_mobile/widgets/icon_text.dart';
import 'package:acs_upb_mobile/widgets/scaffold.dart';
import 'package:acs_upb_mobile/widgets/spoiler.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:black_hole_flutter/black_hole_flutter.dart';
import 'package:flutter/material.dart';
import 'package:pedantic/pedantic.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -383,29 +382,9 @@ class _ClassListItemState extends State<ClassListItem> {
@override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(
backgroundColor: widget.classHeader.colorFromAcronym,
child: Container(
width: 30,
child: (widget.selectable && selected)
? Icon(
Icons.check,
color:
widget.classHeader.colorFromAcronym.highEmphasisOnColor,
)
: Align(
alignment: Alignment.center,
child: AutoSizeText(
widget.classHeader.acronym,
minFontSize: 0,
maxLines: 1,
style: TextStyle(
color: widget
.classHeader.colorFromAcronym.highEmphasisOnColor,
),
),
),
),
leading: ClassIcon(
classHeader: widget.classHeader,
selected: widget.selectable && selected,
),
title: Text(
widget.classHeader.name,
Expand Down
24 changes: 24 additions & 0 deletions lib/pages/people/service/person_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,28 @@ class PersonProvider with ChangeNotifier {
return null;
}
}

Future<String> mostRecentLecturer(String classId,
{BuildContext context}) async {
try {
final QuerySnapshot query = await FirebaseFirestore.instance
.collection('events')
.where('class', isEqualTo: classId)
.where('type', isEqualTo: 'lecture')
.orderBy('start', descending: true)
.limit(1)
.get();

if (query == null || query.docs.isEmpty) {
return null;
}
return query.docs.first.get('teacher');
} catch (e) {
print(e);
if (context != null) {
AppToast.show(S.of(context).errorSomethingWentWrong);
}
return null;
}
}
}
43 changes: 29 additions & 14 deletions lib/pages/timetable/view/events/event_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:acs_upb_mobile/pages/classes/view/class_view.dart';
import 'package:acs_upb_mobile/pages/classes/view/classes_page.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/pages/people/view/person_view.dart';
import 'package:acs_upb_mobile/pages/timetable/model/events/class_event.dart';
import 'package:acs_upb_mobile/pages/timetable/model/events/recurring_event.dart';
import 'package:acs_upb_mobile/pages/timetable/model/events/uni_event.dart';
Expand Down Expand Up @@ -174,20 +175,34 @@ class _EventViewState extends State<EventView> {
if (widget.eventInstance.mainEvent is ClassEvent)
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Row(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.person),
),
const SizedBox(width: 16),
Text(
(widget.eventInstance.mainEvent as ClassEvent)
.teacher
.name ??
S.of(context).labelUnknown,
style: Theme.of(context).textTheme.subtitle1),
],
child: GestureDetector(
onTap: () {
if ((widget.eventInstance.mainEvent as ClassEvent).teacher !=
null) {
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (BuildContext buildContext) => PersonView(
person:
(widget.eventInstance.mainEvent as ClassEvent)
.teacher));
}
},
child: Row(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.person),
),
const SizedBox(width: 16),
Text(
(widget.eventInstance.mainEvent as ClassEvent)
.teacher
.name ??
S.of(context).labelUnknown,
style: Theme.of(context).textTheme.subtitle1),
],
),
),
),
]),
Expand Down
59 changes: 59 additions & 0 deletions lib/widgets/class_icon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:acs_upb_mobile/pages/classes/model/class.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:black_hole_flutter/black_hole_flutter.dart';

extension ClassExtension on ClassHeader {
Color get colorFromAcronym {
int r = 0, g = 0, b = 0;
if (acronym.isNotEmpty) {
b = acronym[0].codeUnitAt(0);
if (acronym.length >= 2) {
g = acronym[1].codeUnitAt(0);
if (acronym.length >= 3) {
r = acronym[2].codeUnitAt(0);
}
}
}
const int brightnessFactor = 2;
return Color.fromRGBO(
r * brightnessFactor, g * brightnessFactor, b * brightnessFactor, 1);
}
}

class ClassIcon extends StatelessWidget {
const ClassIcon({
@required this.classHeader,
Key key,
this.selected = false,
}) : super(key: key);

final ClassHeader classHeader;
final bool selected;

@override
Widget build(BuildContext context) {
return CircleAvatar(
backgroundColor: classHeader.colorFromAcronym,
child: Container(
width: 30,
child: selected
? Icon(
Icons.check,
color: classHeader.colorFromAcronym.highEmphasisOnColor,
)
: Align(
alignment: Alignment.center,
child: AutoSizeText(
classHeader.acronym,
minFontSize: 0,
maxLines: 1,
style: TextStyle(
color: classHeader.colorFromAcronym.highEmphasisOnColor,
),
),
),
),
);
}
}
Loading

0 comments on commit 0337750

Please sign in to comment.