Skip to content

Commit

Permalink
Test timetable (#96)
Browse files Browse the repository at this point in the history
* Add tests for timetable

* Add test for event page

* Increase coverage even more

* Add "add event" test

* Open all day event

* Set default event widget color to type color if available

* Bump build number

* Delete commented code
  • Loading branch information
IoanaAlexandru authored Oct 21, 2020
1 parent bb6ec7b commit 7f93c5f
Show file tree
Hide file tree
Showing 11 changed files with 932 additions and 315 deletions.
6 changes: 3 additions & 3 deletions lib/pages/settings/view/request_permissions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class RequestPermissions extends StatefulWidget {
class RequestPermissionsPage extends StatefulWidget {
static const String routeName = '/requestPermissions';

@override
State<StatefulWidget> createState() => _RequestPermissionsState();
State<StatefulWidget> createState() => _RequestPermissionsPageState();
}

class _RequestPermissionsState extends State<RequestPermissions> {
class _RequestPermissionsPageState extends State<RequestPermissionsPage> {
User user;
bool agreedToResponsibilities = false;
TextEditingController requestController = TextEditingController();
Expand Down
5 changes: 3 additions & 2 deletions lib/pages/settings/view/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ class SettingsPageState extends State<SettingsPage> {
else
{
Navigator.of(context).push(
MaterialPageRoute<RequestPermissions>(
builder: (_) => RequestPermissions())),
MaterialPageRoute<RequestPermissionsPage>(
builder: (_) =>
RequestPermissionsPage())),
}
},
child: Text(S.of(context).labelAskPermissions,
Expand Down
131 changes: 57 additions & 74 deletions lib/pages/timetable/model/events/recurring_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class RecurringUniEvent extends UniEvent {
ClassHeader classHeader,
AcademicCalendar calendar,
String addedBy,
}) : super(
}) : assert(rrule != null),
super(
name: name,
location: location,
start: start,
Expand All @@ -76,87 +77,69 @@ class RecurringUniEvent extends UniEvent {
@override
Iterable<UniEventInstance> generateInstances(
{DateInterval intersectingInterval}) sync* {
if (rrule == null) {
RecurrenceRule rrule = this.rrule;
if (calendar != null && rrule.frequency == Frequency.weekly) {
var weeks = calendar.nonHolidayWeeks;

// Get the correct sequence of weeks for this event.
//
// For example, if the first academic calendar week is 40 and the event
// starts on week 41 and repeats every two weeks - get every odd-index
// week in the non holiday weeks.
// This is necessary because if an "even" week is followed by a one-week
// holiday, the week that comes after the holiday should be considered
// an "odd" week, even though its number in the calendar would have the
// same parity as the week before the holiday.
if (rrule.interval != 1) {
weeks = weeks
.whereIndex((index) =>
index % rrule.interval !=
weeks.lookup(WeekYearRules.iso
.getWeekOfWeekYear(start.calendarDate)) %
rrule.interval)
.toSet();
}
rrule = rrule.copyWith(
frequency: Frequency.daily,
interval: 1,
byWeekDays: rrule.byWeekDays.isNotEmpty
? rrule.byWeekDays
: {ByWeekDayEntry(start.dayOfWeek)},
byWeeks: weeks);
}

// Calculate recurrences
int i = 0;
for (final start in rrule.getInstances(start: start)) {
final LocalDateTime end = start.add(duration);
if (intersectingInterval != null) {
if (end.calendarDate < intersectingInterval.start ||
start.calendarDate > intersectingInterval.end) return;
if (end.calendarDate < intersectingInterval.start) continue;
if (start.calendarDate > intersectingInterval.end) break;
}

yield UniEventInstance(
id: id,
title: name,
mainEvent: this,
color: color,
start: start,
end: start.add(duration),
location: location,
);
} else {
RecurrenceRule rrule = this.rrule;
if (calendar != null && rrule.frequency == Frequency.weekly) {
var weeks = calendar.nonHolidayWeeks;

// Get the correct sequence of weeks for this event.
//
// For example, if the first academic calendar week is 40 and the event
// starts on week 41 and repeats every two weeks - get every odd-index
// week in the non holiday weeks.
// This is necessary because if an "even" week is followed by a one-week
// holiday, the week that comes after the holiday should be considered
// an "odd" week, even though its number in the calendar would have the
// same parity as the week before the holiday.
if (rrule.interval != 1) {
weeks = weeks
.whereIndex((index) =>
index % rrule.interval !=
weeks.lookup(WeekYearRules.iso
.getWeekOfWeekYear(start.calendarDate)) %
rrule.interval)
.toSet();
bool skip = false;
for (final holiday in calendar?.holidays ?? []) {
final holidayInterval =
DateInterval(holiday.startDate, holiday.endDate);
if (holidayInterval.contains(start.calendarDate)) {
// Skip holidays
skip = true;
}
rrule = rrule.copyWith(
frequency: Frequency.daily,
interval: 1,
byWeekDays: rrule.byWeekDays.isNotEmpty
? rrule.byWeekDays
: {ByWeekDayEntry(start.dayOfWeek)},
byWeeks: weeks);
}

// Calculate recurrences
int i = 0;
for (final start in rrule.getInstances(start: start)) {
final LocalDateTime end = start.add(duration);
if (intersectingInterval != null) {
if (end.calendarDate < intersectingInterval.start) continue;
if (start.calendarDate > intersectingInterval.end) break;
}

bool skip = false;
for (final holiday in calendar?.holidays ?? []) {
final holidayInterval =
DateInterval(holiday.startDate, holiday.endDate);
if (holidayInterval.contains(start.calendarDate)) {
// Skip holidays
skip = true;
}
}

if (!skip) {
yield UniEventInstance(
id: '$id-$i',
title: name,
mainEvent: this,
color: color,
start: start,
end: end,
location: location,
);
}

i++;
if (!skip) {
yield UniEventInstance(
id: '$id-$i',
title: name,
mainEvent: this,
color: color,
start: start,
end: end,
location: location,
);
}

i++;
}
}
}
56 changes: 30 additions & 26 deletions lib/pages/timetable/view/events/add_event_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ class _AddEventViewState extends State<AddEventView> {
WeekType.odd: null,
WeekType.even: null,
};
Map<DayOfWeek, bool> weekDaySelected = {
DayOfWeek.monday: false,
DayOfWeek.tuesday: false,
DayOfWeek.wednesday: false,
DayOfWeek.thursday: false,
DayOfWeek.friday: false,
DayOfWeek.saturday: false,
DayOfWeek.sunday: false,
Map<_DayOfWeek, bool> weekDaySelected = {
_DayOfWeek.monday: false,
_DayOfWeek.tuesday: false,
_DayOfWeek.wednesday: false,
_DayOfWeek.thursday: false,
_DayOfWeek.friday: false,
_DayOfWeek.saturday: false,
_DayOfWeek.sunday: false,
};

// TODO(IoanaAlexandru): Make default semester the one closest to now
Expand Down Expand Up @@ -127,14 +127,19 @@ class _AddEventViewState extends State<AddEventView> {
startTime = LocalTime(startHour, 0, 0);

var initialWeekDays = [
DayOfWeek.from(widget.initialEvent?.start?.dayOfWeek) ?? DayOfWeek.monday
_DayOfWeek.from(widget.initialEvent?.start?.dayOfWeek) ??
_DayOfWeek.monday
];
if (widget.initialEvent != null &&
widget.initialEvent is RecurringUniEvent) {
widget.initialEvent is RecurringUniEvent &&
(widget.initialEvent as RecurringUniEvent)
.rrule
.byWeekDays
.isNotEmpty) {
initialWeekDays = (widget.initialEvent as RecurringUniEvent)
.rrule
.byWeekDays
.map((entry) => DayOfWeek.from(entry.day))
.map((entry) => _DayOfWeek.from(entry.day))
.toList();
}
for (final initialWeekDay in initialWeekDays) {
Expand Down Expand Up @@ -334,7 +339,7 @@ class _AddEventViewState extends State<AddEventView> {
onTap: () async {
final res =
await Provider.of<UniEventProvider>(context, listen: false)
.deleteEvent(widget.initialEvent);
.deleteEvent(widget.initialEvent, context: context);
if (res) {
Navigator.of(context)
.popUntil(ModalRoute.withName(Routes.home));
Expand All @@ -358,7 +363,7 @@ class _AddEventViewState extends State<AddEventView> {

final rrule = RecurrenceRule(
frequency: Frequency.weekly,
byWeekDays: (Map<DayOfWeek, bool>.from(weekDaySelected)
byWeekDays: (Map<_DayOfWeek, bool>.from(weekDaySelected)
..removeWhere((key, value) => !value))
.keys
.map((weekDay) => ByWeekDayEntry(weekDay))
Expand Down Expand Up @@ -387,15 +392,15 @@ class _AddEventViewState extends State<AddEventView> {
if (widget.initialEvent?.id == null) {
final res =
await Provider.of<UniEventProvider>(context, listen: false)
.addEvent(event);
.addEvent(event, context: context);
if (res) {
Navigator.of(context).pop();
AppToast.show(S.of(context).messageEventAdded);
}
} else {
final res =
await Provider.of<UniEventProvider>(context, listen: false)
.updateEvent(event);
.updateEvent(event, context: context);
if (res) {
Navigator.of(context).popUntil(ModalRoute.withName(Routes.home));
AppToast.show(S.of(context).messageEventEdited);
Expand Down Expand Up @@ -621,10 +626,10 @@ class SelectableFormField extends FormField<Map<Localizable, bool>> {
);
}

class DayOfWeek extends time_machine.DayOfWeek with Localizable {
const DayOfWeek(int value) : super(value);
class _DayOfWeek extends time_machine.DayOfWeek with Localizable {
const _DayOfWeek(int value) : super(value);

DayOfWeek.from(time_machine.DayOfWeek dayOfWeek) : super(dayOfWeek.value);
_DayOfWeek.from(time_machine.DayOfWeek dayOfWeek) : super(dayOfWeek.value);

@override
String toLocalizedString(BuildContext context) {
Expand All @@ -634,14 +639,13 @@ class DayOfWeek extends time_machine.DayOfWeek with Localizable {
.substring(0, 3);
}

static const DayOfWeek none = DayOfWeek(0);
static const DayOfWeek monday = DayOfWeek(1);
static const DayOfWeek tuesday = DayOfWeek(2);
static const DayOfWeek wednesday = DayOfWeek(3);
static const DayOfWeek thursday = DayOfWeek(4);
static const DayOfWeek friday = DayOfWeek(5);
static const DayOfWeek saturday = DayOfWeek(6);
static const DayOfWeek sunday = DayOfWeek(7);
static const _DayOfWeek monday = _DayOfWeek(1);
static const _DayOfWeek tuesday = _DayOfWeek(2);
static const _DayOfWeek wednesday = _DayOfWeek(3);
static const _DayOfWeek thursday = _DayOfWeek(4);
static const _DayOfWeek friday = _DayOfWeek(5);
static const _DayOfWeek saturday = _DayOfWeek(6);
static const _DayOfWeek sunday = _DayOfWeek(7);
}

class WeekType with Localizable {
Expand Down
10 changes: 8 additions & 2 deletions lib/pages/timetable/view/events/all_day_event_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ class UniAllDayEventWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
final color = event.color ??
event?.mainEvent?.color ??
Theme.of(context).primaryColor;

return Padding(
padding: const EdgeInsets.all(2),
child: CustomPaint(
painter: AllDayEventBackgroundPainter(
info: info,
color: event.color,
color: color,
borderRadius: borderRadius,
),
child: Material(
Expand All @@ -57,14 +61,16 @@ class UniAllDayEventWidget extends StatelessWidget {
}

Widget _buildContent(BuildContext context) {
final color = event.color ?? Theme.of(context).primaryColor;

return Padding(
padding: const EdgeInsets.fromLTRB(4, 2, 0, 2),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: DefaultTextStyle(
style: context.textTheme.bodyText2.copyWith(
fontSize: 14,
color: event.color.highEmphasisOnColor,
color: color.highEmphasisOnColor,
),
child: Text(
event.title ?? event.mainEvent?.classHeader?.acronym,
Expand Down
4 changes: 3 additions & 1 deletion lib/pages/timetable/view/events/event_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class UniEventWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
final color = event.color ?? Theme.of(context).primaryColor;
final color = event.color ??
event?.mainEvent?.color ??
Theme.of(context).primaryColor;

return GestureDetector(
onTap: () => Navigator.of(context).push(MaterialPageRoute<EventView>(
Expand Down
12 changes: 6 additions & 6 deletions lib/pages/timetable/view/timetable_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ class _TimetablePageState extends State<TimetablePage> {
final classProvider = Provider.of<ClassProvider>(context);
final authProvider = Provider.of<AuthProvider>(context);
final filterProvider = Provider.of<FilterProvider>(context);
final requestProvider = Provider.of<RequestProvider>(context);
final user = authProvider.currentUserFromCache;

if (classProvider.userClassHeadersCache?.isEmpty ?? true) {
Expand Down Expand Up @@ -219,7 +218,7 @@ class _TimetablePageState extends State<TimetablePage> {
)
],
);
} else if (filterProvider.cachedFilter.relevantNodes.length < 6) {
} else if ((filterProvider.cachedFilter?.relevantNodes?.length ?? 0) < 6) {
return AppDialog(
title: S.of(context).warningNoEvents,
content: [
Expand Down Expand Up @@ -254,8 +253,8 @@ class _TimetablePageState extends State<TimetablePage> {
)
],
);
} else if (user.permissionLevel < 3 &&
requestProvider.userAlreadyRequestedCache) {
} else if (user.permissionLevel < 3) {
// TODO(IoanaAlexandru): Check if user already requested and show a different message
return AppDialog(
title: S.of(context).warningNoEvents,
content: [Text(S.of(context).messageYouCanContribute)],
Expand All @@ -274,8 +273,8 @@ class _TimetablePageState extends State<TimetablePage> {
S.of(context).messageEmailNotVerifiedToPerformAction);
} else {
await Navigator.of(context).push(
MaterialPageRoute<RequestPermissions>(
builder: (_) => RequestPermissions()));
MaterialPageRoute<RequestPermissionsPage>(
builder: (_) => RequestPermissionsPage()));
}
},
)
Expand All @@ -286,6 +285,7 @@ class _TimetablePageState extends State<TimetablePage> {
title: S.of(context).warningNoEvents,
content: [
RichText(
key: const ValueKey('no_events_message'),
text: TextSpan(
style: Theme.of(context).textTheme.subtitle1,
children: [
Expand Down
Loading

0 comments on commit 7f93c5f

Please sign in to comment.