student-legacy/refilc_kreta_api/lib/controllers/timetable_controller.dart
2025-01-27 18:21:17 +01:00

246 lines
6.6 KiB
Dart

// ignore_for_file: avoid_print, use_build_context_synchronously
import 'dart:async';
import 'dart:developer';
import 'dart:math' as math;
import 'package:refilc_kreta_api/providers/homework_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:refilc_kreta_api/models/lesson.dart';
import 'package:refilc_kreta_api/models/week.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
enum LoadType { initial, offline, loading, online }
class TimetableController extends ChangeNotifier {
late Week currentWeek;
int currentWeekId = -1;
late int previousWeekId;
List<List<Lesson>>? days;
LoadType loadType = LoadType.initial;
TimetableController() {
current();
}
static int getWeekId(Week week) =>
(week.start.difference(getSchoolYearStart()).inDays /
DateTime.daysPerWeek)
.ceil();
static DateTime getSchoolYearStart() {
DateTime now = DateTime.now();
DateTime nowStart = _getYearStart(now.year);
if (nowStart.isBefore(now)) {
return nowStart;
} else {
return _getYearStart(now.year - 1);
}
}
static DateTime _getYearStart(int year) {
var s1 = DateTime(year, DateTime.september, 1);
if (s1.weekday == 6) {
s1.add(const Duration(days: 2));
} else if (s1.weekday == 7) {
s1.add(const Duration(days: 1));
}
return s1;
}
// Jump shortcuts
Future<void> next(BuildContext context) =>
jump(Week.fromId(currentWeekId + 1), context: context);
Future<void> previous(BuildContext context) =>
jump(Week.fromId(currentWeekId - 1), context: context);
void current() {
Week week = Week.current();
int id = getWeekId(week);
if (id > 51) id = 51;
if (id < 0) id = 0;
_setWeek(Week.fromId(id));
}
Future<void> jump(Week week,
{required BuildContext context,
bool initial = false,
bool skip = false,
bool loader = true}) async {
if (_setWeek(week)) return;
loadType = LoadType.initial;
if (loader) {
days = null;
// Don't start loading on init
if (!initial) notifyListeners();
}
days = _sortDays(week, context: context);
if (week != currentWeek) return;
loadType = LoadType.loading;
notifyListeners();
try {
await _fetchWeek(week, context: context).timeout(
const Duration(seconds: 5),
onTimeout: (() => throw "timeout"));
loadType = LoadType.online;
} catch (error, stack) {
print("ERROR: TimetableController.jump: $error\n$stack");
loadType = LoadType.offline;
}
if (week != currentWeek) return;
days = _sortDays(week, context: context);
if (week != currentWeek) return;
// Jump to next week on weekends
if (skip &&
(days?.length ?? 0) > 0 &&
days!.last.last.end.isBefore(DateTime.now())) {
return next(context);
}
notifyListeners();
}
bool _setWeek(Week week) {
int id = getWeekId(week);
if (id > 51) return true; // Max 52.
if (id < 0) return true; // Min 1.
// Set week start to Sept. 1 of first week
if (!_differentDate(week.start, Week.fromId(0).start)) {
week.start = TimetableController.getSchoolYearStart();
}
currentWeek = week;
previousWeekId = currentWeekId;
currentWeekId = id;
return false;
}
Future<void> _fetchWeek(Week week, {required BuildContext context}) async {
await Future.wait([
context.read<TimetableProvider>().fetch(week: week),
context.read<HomeworkProvider>().fetch(from: week.start, db: false),
]);
}
List<List<Lesson>> _sortDays(Week week, {required BuildContext context}) {
List<List<Lesson>> days = [];
try {
final timetableProvider = context.read<TimetableProvider>();
List<Lesson> lessons = timetableProvider.getWeek(week) ?? [];
if (lessons.isNotEmpty) {
days.add([]);
lessons.sort((a, b) => a.date.compareTo(b.date));
for (var lesson in lessons) {
if (days.last.isNotEmpty &&
_differentDate(lesson.date, days.last.last.date)) {
days.add([]);
}
days.last.add(lesson);
}
for (int i = 0; i < days.length; i++) {
List<Lesson> day0 = List.castFrom(days[i]);
List<int> lessonIndexes = _getIndexes(day0);
int minIndex = 0, maxIndex = 0;
if (lessonIndexes.isNotEmpty) {
minIndex = lessonIndexes.reduce(math.min);
maxIndex = lessonIndexes.reduce(math.max);
}
List<Lesson> day = [];
if (lessonIndexes.isNotEmpty) {
// Fill missing indexes with empty spaces
for (var i in List<int>.generate(
maxIndex - minIndex + 1, (int i) => minIndex + i)) {
List<Lesson> indexLessons = _getLessonsByIndex(day0, i);
// Empty lesson
if (indexLessons.isEmpty) {
// Get start date by previous lesson
List<Lesson> prevLesson = _getLessonsByIndex(day, i - 1);
try {
DateTime startDate =
prevLesson.last.start.add(const Duration(seconds: 1));
indexLessons.add(Lesson.fromJson({
'isEmpty': true,
'Oraszam': i,
'KezdetIdopont': startDate.toIso8601String()
}));
// ignore: empty_catches
} catch (e) {}
}
day.addAll(indexLessons);
}
}
// Additional lessons
day.addAll(day0.where((l) =>
int.tryParse(l.lessonIndex) == null && l.subject.id != ''));
day.sort((a, b) => a.start.compareTo(b.start));
// Special Dates
for (var l in day0) {
l.subject.id == '' ? day.insert(0, l) : null;
}
days[i] = day;
}
}
} catch (e) {
log("_sortDays error: $e");
}
return days;
}
List<Lesson> _getLessonsByIndex(List<Lesson> lessons, int index) {
List<Lesson> ret = [];
for (var lesson in lessons) {
int? lessonIndex = int.tryParse(lesson.lessonIndex);
if (lessonIndex != null && lessonIndex == index) {
ret.add(lesson);
}
}
return ret;
}
List<int> _getIndexes(List<Lesson> lessons) {
List<int> indexes = [];
for (var l in lessons) {
int? index = int.tryParse(l.lessonIndex);
if (index != null) indexes.add(index);
}
return indexes;
}
bool _differentDate(DateTime a, DateTime b) =>
!(a.year == b.year && a.month == b.month && a.day == b.day);
}