Add files via upload

This commit is contained in:
ReinerRego
2023-05-26 21:48:51 +02:00
committed by GitHub
parent 59b5acc79b
commit 258a6ab8d3
64 changed files with 6779 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, Filc
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,82 @@
import 'package:intl/intl.dart';
class KretaAPI {
// IDP API
static const login = BaseKreta.kretaIdp + KretaApiEndpoints.token;
static const nonce = BaseKreta.kretaIdp + KretaApiEndpoints.nonce;
static const clientId = "kreta-ellenorzo-mobile-android";
// ELLENORZO API
static String notes(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.notes;
static String events(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.events;
static String student(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.student;
static String grades(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.grades;
static String absences(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.absences;
static String groups(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.groups;
static String groupAverages(String iss, String uid) =>
BaseKreta.kreta(iss) + KretaApiEndpoints.groupAverages + "?oktatasiNevelesiFeladatUid=" + uid;
static String timetable(String iss, {DateTime? start, DateTime? end}) =>
BaseKreta.kreta(iss) +
KretaApiEndpoints.timetable +
(start != null && end != null ? "?datumTol=" + start.toUtc().toIso8601String() + "&datumIg=" + end.toUtc().toIso8601String() : "");
static String exams(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.exams;
static String homework(String iss, {DateTime? start, String? id}) =>
BaseKreta.kreta(iss) +
KretaApiEndpoints.homework +
(id != null ? "/$id" : "") +
(id == null && start != null ? "?datumTol=" + DateFormat('yyyy-MM-dd').format(start) : "");
static String capabilities(String iss) => BaseKreta.kreta(iss) + KretaApiEndpoints.capabilities;
static String downloadHomeworkAttachments(String iss, String uid, String type) =>
BaseKreta.kreta(iss) + KretaApiEndpoints.downloadHomeworkAttachments(uid, type);
// ADMIN API
static const sendMessage = BaseKreta.kretaAdmin + KretaAdminEndpoints.sendMessage;
static String messages(String endpoint) => BaseKreta.kretaAdmin + KretaAdminEndpoints.messages(endpoint);
static String message(String id) => BaseKreta.kretaAdmin + KretaAdminEndpoints.message(id);
static const recipientCategories = BaseKreta.kretaAdmin + KretaAdminEndpoints.recipientCategories;
static const availableCategories = BaseKreta.kretaAdmin + KretaAdminEndpoints.availableCategories;
static const recipientsTeacher = BaseKreta.kretaAdmin + KretaAdminEndpoints.recipientsTeacher;
static const uploadAttachment = BaseKreta.kretaAdmin + KretaAdminEndpoints.uploadAttachment;
static String downloadAttachment(String id) => BaseKreta.kretaAdmin + KretaAdminEndpoints.downloadAttachment(id);
static const trashMessage = BaseKreta.kretaAdmin + KretaAdminEndpoints.trashMessage;
static const deleteMessage = BaseKreta.kretaAdmin + KretaAdminEndpoints.deleteMessage;
}
class BaseKreta {
static String kreta(String iss) => "https://$iss.e-kreta.hu";
static const kretaIdp = "https://idp.e-kreta.hu";
static const kretaAdmin = "https://eugyintezes.e-kreta.hu";
static const kretaFiles = "https://files.e-kreta.hu";
}
class KretaApiEndpoints {
static const token = "/connect/token";
static const nonce = "/nonce";
static const notes = "/ellenorzo/V3/Sajat/Feljegyzesek";
static const events = "/ellenorzo/V3/Sajat/FaliujsagElemek";
static const student = "/ellenorzo/V3/Sajat/TanuloAdatlap";
static const grades = "/ellenorzo/V3/Sajat/Ertekelesek";
static const absences = "/ellenorzo/V3/Sajat/Mulasztasok";
static const groups = "/ellenorzo/V3/Sajat/OsztalyCsoportok";
static const groupAverages = "/ellenorzo/V3/Sajat/Ertekelesek/Atlagok/OsztalyAtlagok";
static const timetable = "/ellenorzo/V3/Sajat/OrarendElemek";
static const exams = "/ellenorzo/V3/Sajat/BejelentettSzamonkeresek";
static const homework = "/ellenorzo/V3/Sajat/HaziFeladatok";
// static const homeworkDone = "/ellenorzo/V3/Sajat/HaziFeladatok/Megoldva"; // Removed from the API
static const capabilities = "/ellenorzo/V3/Sajat/Intezmenyek";
static String downloadHomeworkAttachments(String uid, String type) => "/ellenorzo/V3/Sajat/Csatolmany/$uid";
}
class KretaAdminEndpoints {
//static const messages = "/api/v1/kommunikacio/postaladaelemek/sajat";
static const sendMessage = "/api/v1/kommunikacio/uzenetek";
static String messages(String endpoint) => "/api/v1/kommunikacio/postaladaelemek/$endpoint";
static String message(String id) => "/api/v1/kommunikacio/postaladaelemek/$id";
static const recipientCategories = "/api/v1/adatszotarak/cimzetttipusok";
static const availableCategories = "/api/v1/kommunikacio/cimezhetotipusok";
static const recipientsTeacher = "/api/v1/kreta/alkalmazottak/tanar";
static const uploadAttachment = "/ideiglenesfajlok";
static String downloadAttachment(String id) => "/api/v1/dokumentumok/uzenetek/$id";
static const trashMessage = "/api/v1/kommunikacio/postaladaelemek/kuka";
static const deleteMessage = "/api/v1/kommunikacio/postaladaelemek/torles";
}

View File

@@ -0,0 +1,193 @@
// ignore_for_file: avoid_print
import 'dart:convert';
import 'dart:io';
import 'package:filcnaplo/api/login.dart';
import 'package:filcnaplo/api/nonce.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo/utils/jwt.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http;
import 'dart:async';
class KretaClient {
String? accessToken;
String? refreshToken;
String? idToken;
String? userAgent;
late http.Client client;
late final SettingsProvider _settings;
late final UserProvider _user;
late final StatusProvider _status;
KretaClient({
this.accessToken,
required SettingsProvider settings,
required UserProvider user,
required StatusProvider status,
}) : _settings = settings,
_user = user,
_status = status,
userAgent = settings.config.userAgent {
var ioclient = HttpClient();
ioclient.badCertificateCallback = _checkCerts;
client = http.IOClient(ioclient);
}
bool _checkCerts(X509Certificate cert, String host, int port) {
return _settings.developerMode;
}
Future<dynamic> getAPI(
String url, {
Map<String, String>? headers,
bool autoHeader = true,
bool json = true,
bool rawResponse = false,
}) async {
Map<String, String> headerMap;
if (rawResponse) json = false;
if (headers != null) {
headerMap = headers;
} else {
headerMap = {};
}
try {
http.Response? res;
for (int i = 0; i < 3; i++) {
if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) headerMap["authorization"] = "Bearer $accessToken";
if (!headerMap.containsKey("user-agent") && userAgent != null) headerMap["user-agent"] = "$userAgent";
}
res = await client.get(Uri.parse(url), headers: headerMap);
_status.triggerRequest(res);
if (res.statusCode == 401) {
await refreshLogin();
headerMap.remove("authorization");
} else {
break;
}
// Wait before retrying
await Future.delayed(const Duration(milliseconds: 500));
}
if (res == null) throw "Login error";
if (json) {
return jsonDecode(res.body);
} else if (rawResponse) {
return res.bodyBytes;
} else {
return res.body;
}
} on http.ClientException catch (error) {
print("ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}");
} catch (error) {
print("ERROR: KretaClient.getAPI ($url) ${error.runtimeType}: $error");
}
}
Future<dynamic> postAPI(
String url, {
Map<String, String>? headers,
bool autoHeader = true,
bool json = true,
Object? body,
}) async {
Map<String, String> headerMap;
if (headers != null) {
headerMap = headers;
} else {
headerMap = {};
}
try {
http.Response? res;
for (int i = 0; i < 3; i++) {
if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) headerMap["authorization"] = "Bearer $accessToken";
if (!headerMap.containsKey("user-agent") && userAgent != null) headerMap["user-agent"] = "$userAgent";
if (!headerMap.containsKey("content-type")) headerMap["content-type"] = "application/json";
}
res = await client.post(Uri.parse(url), headers: headerMap, body: body);
if (res.statusCode == 401) {
await refreshLogin();
headerMap.remove("authorization");
} else {
break;
}
}
if (res == null) throw "Login error";
if (json) {
return jsonDecode(res.body);
} else {
return res.body;
}
} on http.ClientException catch (error) {
print("ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}");
} catch (error) {
print("ERROR: KretaClient.getAPI ($url) ${error.runtimeType}: $error");
}
}
Future<void> refreshLogin() async {
User? loginUser = _user.user;
if (loginUser == null) return;
Map<String, String> headers = {
"content-type": "application/x-www-form-urlencoded",
};
String nonceStr = await getAPI(KretaAPI.nonce, json: false);
Nonce nonce = getNonce(nonceStr, loginUser.username, loginUser.instituteCode);
headers.addAll(nonce.header());
if (_settings.presentationMode) {
print("DEBUG: refreshLogin: ${loginUser.id}");
} else {
print("DEBUG: refreshLogin: ${loginUser.id} ${loginUser.name}");
}
Map? loginRes = await postAPI(KretaAPI.login,
headers: headers,
body: User.loginBody(
username: loginUser.username,
password: loginUser.password,
instituteCode: loginUser.instituteCode,
));
if (loginRes != null) {
if (loginRes.containsKey("access_token")) accessToken = loginRes["access_token"];
if (loginRes.containsKey("refresh_token")) refreshToken = loginRes["refresh_token"];
// Update role
loginUser.role = JwtUtils.getRoleFromJWT(accessToken ?? "") ?? Role.student;
}
if (refreshToken != null) {
Map? refreshRes = await postAPI(KretaAPI.login,
headers: headers, body: User.refreshBody(refreshToken: refreshToken!, instituteCode: loginUser.instituteCode));
if (refreshRes != null) {
if (refreshRes.containsKey("id_token")) idToken = refreshRes["id_token"];
}
}
}
}

View File

@@ -0,0 +1,217 @@
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:developer';
import 'dart:math' as math;
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo_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 = [];
final timetableProvider = context.read<TimetableProvider>();
try {
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> _day = List.castFrom(days[i]);
List<int> lessonIndexes = _getIndexes(_day);
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(_day, 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(_day.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 _day) {
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);
}

View File

@@ -0,0 +1,76 @@
import "category.dart";
import "subject.dart";
class Absence {
Map? json;
String id;
DateTime date;
int delay;
DateTime submitDate;
String teacher;
Justification state;
Category? justification;
Category? type;
Category? mode;
Subject subject;
DateTime lessonStart;
DateTime lessonEnd;
int? lessonIndex;
String group;
Absence({
required this.id,
required this.date,
required this.delay,
required this.submitDate,
required this.teacher,
required this.state,
this.justification,
this.type,
this.mode,
required this.subject,
required this.lessonStart,
required this.lessonEnd,
this.lessonIndex,
required this.group,
this.json,
});
factory Absence.fromJson(Map json) {
DateTime lessonStart;
DateTime lessonEnd;
int? lessonIndex;
if (json["Ora"] != null) {
lessonStart = json["Ora"]["KezdoDatum"] != null ? DateTime.parse(json["Ora"]["KezdoDatum"]).toLocal() : DateTime(0);
lessonEnd = json["Ora"]["VegDatum"] != null ? DateTime.parse(json["Ora"]["VegDatum"]).toLocal() : DateTime(0);
lessonIndex = json["Ora"]["Oraszam"];
} else {
lessonStart = DateTime(0);
lessonEnd = DateTime(0);
}
return Absence(
id: json["Uid"],
date: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0),
delay: json["KesesPercben"] ?? 0,
submitDate: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0),
teacher: (json["RogzitoTanarNeve"] ?? "").trim(),
state: json["IgazolasAllapota"] == "Igazolt"
? Justification.excused
: json["IgazolasAllapota"] == "Igazolando"
? Justification.pending
: Justification.unexcused,
justification: json["IgazolasTipusa"] != null ? Category.fromJson(json["IgazolasTipusa"]) : null,
type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null,
mode: json["Mod"] != null ? Category.fromJson(json["Mod"]) : null,
subject: Subject.fromJson(json["Tantargy"] ?? {}),
lessonStart: lessonStart,
lessonEnd: lessonEnd,
lessonIndex: lessonIndex,
group: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] : "",
json: json,
);
}
}
enum Justification { excused, unexcused, pending }

View File

@@ -0,0 +1,32 @@
import 'package:file_picker/file_picker.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
class Attachment {
Map? json;
int id;
PlatformFile? file;
String name;
String? fileId;
String kretaFilePath;
Attachment({
required this.id,
this.file,
required this.name,
this.fileId,
required this.kretaFilePath,
this.json,
});
factory Attachment.fromJson(Map json) {
return Attachment(
id: json["azonosito"],
name: (json["fajlNev"] ?? "attachment").trim(),
kretaFilePath: json["utvonal"] ?? "",
json: json,
);
}
String get downloadUrl => KretaAPI.downloadAttachment(id.toString());
bool get isImage => name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png");
}

View File

@@ -0,0 +1,44 @@
import 'grade.dart';
class Category {
String id;
String description;
String name;
Category({
required this.id,
this.description = "",
this.name = "",
});
factory Category.fromJson(Map json) {
return Category(
id: json["Uid"] ?? "",
description: json["Leiras"] != "Na" ? json["Leiras"] ?? "" : "",
name: json["Nev"] != "Na" ? json["Nev"] ?? "" : "",
);
}
static GradeType getGradeType(String string) {
switch (string) {
case "evkozi_jegy_ertekeles":
return GradeType.midYear;
case "I_ne_jegy_ertekeles":
return GradeType.firstQ;
case "II_ne_jegy_ertekeles":
return GradeType.secondQ;
case "felevi_jegy_ertekeles":
return GradeType.halfYear;
case "III_ne_jegy_ertekeles":
return GradeType.thirdQ;
case "IV_ne_jegy_ertekeles":
return GradeType.fourthQ;
case "evvegi_jegy_ertekeles":
return GradeType.endYear;
case "osztalyozo_vizsga":
return GradeType.levelExam;
default:
return GradeType.unknown;
}
}
}

View File

@@ -0,0 +1,28 @@
class Event {
Map? json;
String id;
DateTime start;
DateTime end;
String title;
String content;
Event({
required this.id,
required this.start,
required this.end,
this.title = "",
this.content = "",
this.json,
});
factory Event.fromJson(Map json) {
return Event(
id: json["Uid"] ?? "",
start: json["ErvenyessegKezdete"] != null ? DateTime.parse(json["ErvenyessegKezdete"]).toLocal() : DateTime(0),
end: json["ErvenyessegVege"] != null ? DateTime.parse(json["ErvenyessegVege"]).toLocal() : DateTime(0),
title: json["Cim"] ?? "",
content: json["Tartalom"] != null ? json["Tartalom"].replaceAll("\r", "") : "",
json: json,
);
}
}

View File

@@ -0,0 +1,42 @@
import 'category.dart';
class Exam {
Map? json;
DateTime date;
DateTime writeDate;
Category? mode;
int? subjectIndex;
String subjectName;
String teacher;
String description;
String group;
String id;
Exam({
required this.id,
required this.date,
required this.writeDate,
this.mode,
this.subjectIndex,
required this.subjectName,
required this.teacher,
required this.description,
required this.group,
this.json,
});
factory Exam.fromJson(Map json) {
return Exam(
id: json["Uid"] ?? "",
date: json["BejelentesDatuma"] != null ? DateTime.parse(json["BejelentesDatuma"]).toLocal() : DateTime(0),
writeDate: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0),
mode: json["Modja"] != null ? Category.fromJson(json["Modja"]) : null,
subjectIndex: json["OrarendiOraOraszama"],
subjectName: json["TantargyNeve"] ?? "",
teacher: (json["RogzitoTanarNeve"] ?? "").trim(),
description: (json["Temaja"] ?? "").trim(),
group: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] ?? "" : "",
json: json,
);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:filcnaplo/utils/format.dart';
import 'category.dart';
import 'subject.dart';
class Grade {
Map? json;
String id;
DateTime date;
GradeValue value;
String teacher;
String description;
GradeType type;
String groupId;
Subject subject;
Category? gradeType;
Category mode;
DateTime writeDate;
DateTime seenDate;
String form;
Grade({
required this.id,
required this.date,
required this.value,
required this.teacher,
required this.description,
required this.type,
required this.groupId,
required this.subject,
this.gradeType,
required this.mode,
required this.writeDate,
required this.seenDate,
required this.form,
this.json,
});
factory Grade.fromJson(Map json) {
return Grade(
id: json["Uid"] ?? "",
date: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0),
value: GradeValue(
json["SzamErtek"] ?? 0,
json["SzovegesErtek"] ?? "",
json["SzovegesErtekelesRovidNev"] ?? "",
json["SulySzazalekErteke"] ?? 0,
percentage: json["ErtekFajta"] != null ? json["ErtekFajta"]["Uid"] == "3,Szazalekos" : false,
),
teacher: (json["ErtekeloTanarNeve"] ?? "").trim(),
description: json["Tema"] ?? "",
type: json["Tipus"] != null ? Category.getGradeType(json["Tipus"]["Nev"]) : GradeType.unknown,
groupId: (json["OsztalyCsoport"] ?? {})["Uid"] ?? "",
subject: Subject.fromJson(json["Tantargy"] ?? {}),
gradeType: json["ErtekFajta"] != null ? Category.fromJson(json["ErtekFajta"]) : null,
mode: Category.fromJson(json["Mod"] ?? {}),
writeDate: json["RogzitesDatuma"] != null ? DateTime.parse(json["RogzitesDatuma"]).toLocal() : DateTime(0),
seenDate: json["LattamozasDatuma"] != null ? DateTime.parse(json["LattamozasDatuma"]).toLocal() : DateTime(0),
form: (json["Jelleg"] ?? "Na") != "Na" ? json["Jelleg"] : "",
json: json,
);
}
bool compareTo(dynamic other) {
if (runtimeType != other.runtimeType) return false;
if (id == other.id && seenDate == other.seenDate) {
return true;
}
return false;
}
}
class GradeValue {
int _value;
set value(int v) => _value = v;
int get value {
String _valueName = valueName.toLowerCase().specialChars();
if (_value == 0 && ["peldas", "jo", "valtozo", "rossz", "hanyag"].contains(_valueName)) {
switch (_valueName) {
case "peldas":
return 5;
case "jo":
return 4;
case "valtozo":
return 3;
case "rossz":
return 2;
case "hanyag":
return 2;
}
}
return _value;
}
String _valueName;
set valueName(String v) => _valueName = v;
String get valueName => _valueName.split("(")[0];
String shortName;
int _weight;
set weight(int v) => _weight = v;
int get weight {
String _valueName = valueName.toLowerCase().specialChars();
if (_value == 0 && ["peldas", "jo", "valtozo", "rossz", "hanyag"].contains(_valueName)) {
return 0;
}
return _weight;
}
final bool _percentage;
bool get percentage => _percentage;
GradeValue(int value, String valueName, this.shortName, int weight, {bool percentage = false})
: _value = value,
_valueName = valueName,
_weight = weight,
_percentage = percentage;
}
enum GradeType { midYear, firstQ, secondQ, halfYear, thirdQ, fourthQ, endYear, levelExam, ghost, unknown }

View File

@@ -0,0 +1,19 @@
import 'package:filcnaplo_kreta_api/models/subject.dart';
class GroupAverage {
String uid;
double average;
Subject subject;
Map json;
GroupAverage({required this.uid, required this.average, required this.subject, this.json = const {}});
factory GroupAverage.fromJson(Map json) {
return GroupAverage(
uid: json["Uid"] ?? "",
average: json["OsztalyCsoportAtlag"] ?? 0,
subject: Subject.fromJson(json["Tantargy"] ?? {}),
json: json,
);
}
}

View File

@@ -0,0 +1,69 @@
import 'package:filcnaplo_kreta_api/client/api.dart';
class Homework {
Map? json;
DateTime date;
DateTime lessonDate;
DateTime deadline;
bool byTeacher;
bool homeworkEnabled;
String teacher;
String content;
String subjectName;
String group;
List<HomeworkAttachment> attachments;
String id;
Homework({
required this.date,
required this.lessonDate,
required this.deadline,
required this.byTeacher,
required this.homeworkEnabled,
required this.teacher,
required this.content,
required this.subjectName,
required this.group,
required this.attachments,
required this.id,
this.json,
});
factory Homework.fromJson(Map json) {
return Homework(
id: json["Uid"] ?? "",
date: json["RogzitesIdopontja"] != null ? DateTime.parse(json["RogzitesIdopontja"]).toLocal() : DateTime(0),
lessonDate: json["FeladasDatuma"] != null ? DateTime.parse(json["FeladasDatuma"]).toLocal() : DateTime(0),
deadline: json["HataridoDatuma"] != null ? DateTime.parse(json["HataridoDatuma"]).toLocal() : DateTime(0),
byTeacher: json["IsTanarRogzitette"] ?? true,
homeworkEnabled: json["IsTanuloHaziFeladatEnabled"] ?? false,
teacher: (json["RogzitoTanarNeve"] ?? "").trim(),
content: (json["Szoveg"] ?? "").trim(),
subjectName: json["TantargyNeve"] ?? "",
group: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] ?? "" : "",
attachments: ((json["Csatolmanyok"] ?? []) as List).cast<Map>().map((Map json) => HomeworkAttachment.fromJson(json)).toList(),
json: json,
);
}
}
class HomeworkAttachment {
Map? json;
String id;
String name;
String type;
HomeworkAttachment({required this.id, this.name = "", this.type = "", this.json});
factory HomeworkAttachment.fromJson(Map json) {
return HomeworkAttachment(
id: json["Uid"] ?? "",
name: json["Nev"] ?? "",
type: json["Tipus"] ?? "",
json: json,
);
}
String downloadUrl(String iss) => KretaAPI.downloadHomeworkAttachments(iss, id, type);
bool get isImage => name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png");
}

View File

@@ -0,0 +1,97 @@
import 'subject.dart';
import 'category.dart';
class Lesson {
Map? json;
Category? status;
DateTime date;
Subject subject;
String lessonIndex;
int? lessonYearIndex;
String substituteTeacher;
String teacher;
bool homeworkEnabled;
DateTime start;
DateTime end;
bool studentPresence;
String homeworkId;
String exam;
String id;
Category? type;
String description;
String room;
String groupName;
String name;
bool online;
bool isEmpty;
Lesson({
this.status,
required this.date,
required this.subject,
required this.lessonIndex,
this.lessonYearIndex,
this.substituteTeacher = "",
required this.teacher,
this.homeworkEnabled = false,
required this.start,
required this.end,
this.studentPresence = true,
required this.homeworkId,
this.exam = "",
required this.id,
this.type,
required this.description,
required this.room,
required this.groupName,
required this.name,
this.online = false,
this.isEmpty = false,
this.json,
});
factory Lesson.fromJson(Map json) {
return Lesson(
id: json["Uid"] ?? "",
status: json["Allapot"] != null ? Category.fromJson(json["Allapot"]) : null,
date: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0),
subject: Subject.fromJson(json["Tantargy"] ?? {}),
lessonIndex: json["Oraszam"] != null ? json["Oraszam"].toString() : "+",
lessonYearIndex: json["OraEvesSorszama"],
substituteTeacher: (json["HelyettesTanarNeve"] ?? "").trim(),
teacher: (json["TanarNeve"] ?? "").trim(),
homeworkEnabled: json["IsTanuloHaziFeladatEnabled"] ?? false,
start: json["KezdetIdopont"] != null ? DateTime.parse(json["KezdetIdopont"]).toLocal() : DateTime(0),
studentPresence: json["TanuloJelenlet"] != null
? (json["TanuloJelenlet"]["Nev"] ?? "") == "Hianyzas"
? false
: true
: true,
end: json["VegIdopont"] != null ? DateTime.parse(json["VegIdopont"]).toLocal() : DateTime(0),
homeworkId: json["HaziFeladatUid"] ?? "",
exam: json["BejelentettSzamonkeresUid"] ?? "",
type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null,
description: json["Tema"] ?? "",
room: ((json["TeremNeve"] ?? "").split("_").join(" ") as String).replaceAll(RegExp(r" ?terem ?", caseSensitive: false), ""),
groupName: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Nev"] ?? "" : "",
name: json["Nev"] ?? "",
online: json["IsDigitalisOra"] ?? false,
isEmpty: json['isEmpty'] ?? false,
json: json,
);
}
int? getFloor() {
final match = RegExp(r"(\d{3})").firstMatch(room);
if (match != null) {
final floorNumber = int.tryParse(match[0] ?? "");
if (floorNumber != null) {
return (floorNumber / 100).floor();
}
}
return null;
}
bool get isChanged => status?.name == "Elmaradt" || substituteTeacher != "";
bool get swapDesc => room.length > 8;
}

View File

@@ -0,0 +1,97 @@
import 'recipient.dart';
import 'attachment.dart';
class Message {
Map? json;
int id;
int? replyId;
int messageId;
int? conversationId;
bool seen;
bool deleted;
DateTime date;
String author;
String content;
String subject;
MessageType? type;
List<Recipient> recipients;
List<Attachment> attachments;
Message({
required this.id,
required this.messageId,
required this.seen,
required this.deleted,
required this.date,
required this.author,
required this.content,
required this.subject,
this.type,
required this.recipients,
required this.attachments,
this.replyId,
this.conversationId,
this.json,
});
factory Message.fromJson(Map json, {MessageType? forceType}) {
Map message = json["uzenet"];
MessageType? type = forceType;
if (type == null) {
switch (json["tipus"]["kod"]) {
case "BEERKEZETT":
type = MessageType.inbox;
break;
case "ELKULDOTT":
type = MessageType.sent;
break;
case "PISZKOZAT":
type = MessageType.draft;
break;
}
if (json["isToroltElem"] == true) type = MessageType.trash;
}
return Message(
id: json["azonosito"],
messageId: message["azonosito"],
seen: json["isElolvasva"] ?? false,
deleted: json["isToroltElem"] ?? false,
date: message["kuldesDatum"] != null ? DateTime.parse(message["kuldesDatum"]).toLocal() : DateTime(0),
author: (message["feladoNev"] ?? "").trim(),
content: message["szoveg"].replaceAll("\r", "") ?? "",
subject: message["targy"] ?? "",
type: type,
recipients: (message["cimzettLista"] as List).cast<Map>().map((Map recipient) => Recipient.fromJson(recipient)).toList(),
attachments: (message["csatolmanyok"] as List).cast<Map>().map((Map attachment) => Attachment.fromJson(attachment)).toList(),
replyId: message["elozoUzenetAzonosito"],
conversationId: message["beszelgetesAzonosito"],
json: json,
);
}
bool compareTo(dynamic other) {
if (runtimeType != other.runtimeType) return false;
return id == other.id && seen == other.seen && deleted == other.deleted;
}
}
enum MessageType { inbox, sent, trash, draft }
class Conversation {
int id;
late List<Message> _messages;
List<Message> get messages => _messages;
Conversation({required this.id, List<Message> messages = const []}) {
_messages = List.from(messages);
}
void sort() => _messages.sort((a, b) => -a.date.compareTo(b.date));
void add(Message message) => _messages.add(message);
Message get newest => _messages.first;
}

View File

@@ -0,0 +1,42 @@
import 'category.dart';
class Note {
Map? json;
String id;
String title;
DateTime date;
DateTime submitDate;
String teacher;
DateTime seenDate;
String groupId;
String content;
Category? type;
Note({
required this.id,
required this.title,
required this.date,
required this.submitDate,
required this.teacher,
required this.seenDate,
required this.groupId,
required this.content,
this.type,
this.json,
});
factory Note.fromJson(Map json) {
return Note(
id: json["Uid"] ?? "",
title: json["Cim"] ?? "",
date: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0),
submitDate: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0),
teacher: (json["KeszitoTanarNeve"] ?? "").trim(),
seenDate: json["LattamozasDatuma"] != null ? DateTime.parse(json["LattamozasDatuma"]).toLocal() : DateTime(0),
groupId: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] ?? "" : "",
content: json["Tartalom"].replaceAll("\r", "") ?? "",
type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null,
json: json,
);
}
}

View File

@@ -0,0 +1,56 @@
class Recipient {
Map? json;
int id;
String? studentId; // oktatasi azonosito
int kretaId;
String name;
RecipientCategory? category;
Recipient({
required this.id,
this.studentId,
required this.name,
required this.kretaId,
this.category,
this.json,
});
factory Recipient.fromJson(Map json) {
return Recipient(
id: json["azonosito"],
name: json["nev"] ?? "",
kretaId: json["kretaAzonosito"],
category: json["tipus"] != null ? RecipientCategory.fromJson(json["tipus"]) : null,
json: json,
);
}
}
class RecipientCategory {
Map? json;
int id;
String code;
String shortName;
String name;
String description;
RecipientCategory({
required this.id,
required this.code,
required this.shortName,
required this.name,
required this.description,
this.json,
});
factory RecipientCategory.fromJson(Map json) {
return RecipientCategory(
id: json["azonosito"],
code: json["kod"] ?? "",
shortName: json["rovidNev"] ?? "",
name: json["nev"] ?? "",
description: json["leiras"] ?? "",
json: json,
);
}
}

View File

@@ -0,0 +1,19 @@
class School {
String instituteCode;
String name;
String city;
School({
required this.instituteCode,
required this.name,
required this.city,
});
factory School.fromJson(Map json) {
return School(
instituteCode: json["instituteCode"] ?? "",
name: (json["name"] ?? "").trim(),
city: json["city"] ?? "",
);
}
}

View File

@@ -0,0 +1,55 @@
import 'school.dart';
import 'package:filcnaplo/utils/format.dart';
class Student {
Map? json;
String id;
String name;
School school;
DateTime birth;
String yearId;
String? address;
String? groupId;
List<String> parents;
String? className;
Student({
required this.id,
required this.name,
required this.school,
required this.birth,
required this.yearId,
this.address,
required this.parents,
this.json,
});
factory Student.fromJson(Map json) {
List<String> parents = [];
parents = ((json["Gondviselok"] ?? []) as List).cast<Map>().map((e) => e["Nev"] ?? "").toList().cast<String>();
if (json["AnyjaNeve"] != null) parents.insert(0, json["AnyjaNeve"]);
parents = parents.map((e) => e.capitalize()).toList(); // fix name casing
parents = parents.toSet().toList(); // remove duplicates
return Student(
id: json["Uid"] ?? "",
name: (json["Nev"] ?? json["SzuletesiNev"] ?? "").trim(),
school: School(
instituteCode: json["IntezmenyAzonosito"] ?? "",
name: json["IntezmenyNev"] ?? "",
city: "",
),
birth: json["SzuletesiDatum"] != null ? DateTime.parse(json["SzuletesiDatum"]).toLocal() : DateTime(0),
yearId: json["TanevUid"] ?? "",
address: json["Cimek"] != null
? json["Cimek"].length > 0
? json["Cimek"][0]
: null
: null,
parents: parents,
json: json,
);
}
}

View File

@@ -0,0 +1,30 @@
import 'category.dart';
class Subject {
String id;
Category category;
String name;
String? renamedTo;
bool get isRenamed => renamedTo != null;
Subject({required this.id, required this.category, required this.name, this.renamedTo});
factory Subject.fromJson(Map json) {
final id = json["Uid"] ?? "";
return Subject(
id: id,
category: Category.fromJson(json["Kategoria"] ?? {}),
name: (json["Nev"] ?? "").trim(),
);
}
@override
bool operator ==(other) {
if (other is! Subject) return false;
return id == other.id;
}
@override
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,41 @@
import 'package:filcnaplo_kreta_api/controllers/timetable_controller.dart';
class Week {
DateTime start;
DateTime end;
Week({required this.start, required this.end});
factory Week.current() => Week.fromDate(DateTime.now());
factory Week.fromId(int id) {
DateTime _now = TimetableController.getSchoolYearStart().add(Duration(days: id * DateTime.daysPerWeek));
DateTime now = DateTime(_now.year, _now.month, _now.day);
return Week(
start: now.subtract(Duration(days: now.weekday - 1)),
end: now.add(Duration(days: DateTime.daysPerWeek - now.weekday)),
);
}
factory Week.fromDate(DateTime date) {
// fix #32
DateTime _date = DateTime(date.year, date.month, date.day);
return Week(
start: _date.subtract(Duration(days: _date.weekday - 1)),
end: _date.add(Duration(days: DateTime.daysPerWeek - _date.weekday)),
);
}
Week next() => Week.fromDate(start.add(const Duration(days: 8)));
int get id => -(TimetableController.getSchoolYearStart().difference(start).inDays / DateTime.daysPerWeek).floor();
@override
String toString() => "Week(start: $start, end: $end)";
@override
bool operator ==(Object other) => other is Week && id == other.id;
@override
int get hashCode => id;
}

View File

@@ -0,0 +1,73 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/absence.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AbsenceProvider with ChangeNotifier {
late List<Absence> _absences;
late BuildContext _context;
List<Absence> get absences => _absences;
AbsenceProvider({
List<Absence> initialAbsences = const [],
required BuildContext context,
}) {
_absences = List.castFrom(initialAbsences);
_context = context;
if (_absences.isEmpty) restore();
}
Future<void> restore() async {
String? userId = Provider.of<UserProvider>(_context, listen: false).id;
// Load absences from the database
if (userId != null) {
var dbAbsences = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getAbsences(userId: userId);
_absences = dbAbsences;
await convertBySettings();
}
}
// for renamed subjects
Future<void> convertBySettings() async {
final _database = Provider.of<DatabaseProvider>(_context, listen: false);
Map<String, String> renamedSubjects = (await _database.query.getSettings(_database)).renamedSubjectsEnabled
? await _database.userQuery.renamedSubjects(userId: Provider.of<UserProvider>(_context, listen: false).user!.id)
: {};
for (Absence absence in _absences) {
absence.subject.renamedTo = renamedSubjects.isNotEmpty ? renamedSubjects[absence.subject.id] : null;
}
notifyListeners();
}
// Fetches Absences from the Kreta API then stores them in the database
Future<void> fetch() async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot fetch Absences for User null";
String iss = user.instituteCode;
List? absencesJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.absences(iss));
if (absencesJson == null) throw "Cannot fetch Absences for User ${user.id}";
List<Absence> absences = absencesJson.map((e) => Absence.fromJson(e)).toList();
if (absences.isNotEmpty || _absences.isNotEmpty) await store(absences);
}
// Stores Absences in the database
Future<void> store(List<Absence> absences) async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot store Absences for User null";
String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeAbsences(absences, userId: userId);
_absences = absences;
await convertBySettings();
}
}

View File

@@ -0,0 +1,59 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/event.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class EventProvider with ChangeNotifier {
late List<Event> _events;
late BuildContext _context;
List<Event> get events => _events;
EventProvider({
List<Event> initialEvents = const [],
required BuildContext context,
}) {
_events = List.castFrom(initialEvents);
_context = context;
if (_events.isEmpty) restore();
}
Future<void> restore() async {
String? userId = Provider.of<UserProvider>(_context, listen: false).id;
// Load events from the database
if (userId != null) {
var dbEvents = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getEvents(userId: userId);
_events = dbEvents;
notifyListeners();
}
}
// Fetches Events from the Kreta API then stores them in the database
Future<void> fetch() async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot fetch Events for User null";
String iss = user.instituteCode;
List? eventsJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.events(iss));
if (eventsJson == null) throw "Cannot fetch Events for User ${user.id}";
List<Event> events = eventsJson.map((e) => Event.fromJson(e)).toList();
if (events.isNotEmpty || _events.isNotEmpty) await store(events);
}
// Stores Events in the database
Future<void> store(List<Event> events) async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot store Events for User null";
String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeEvents(events, userId: userId);
_events = events;
notifyListeners();
}
}

View File

@@ -0,0 +1,59 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/exam.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ExamProvider with ChangeNotifier {
late List<Exam> _exams;
late BuildContext _context;
List<Exam> get exams => _exams;
ExamProvider({
List<Exam> initialExams = const [],
required BuildContext context,
}) {
_exams = List.castFrom(initialExams);
_context = context;
if (_exams.isEmpty) restore();
}
Future<void> restore() async {
String? userId = Provider.of<UserProvider>(_context, listen: false).id;
// Load exams from the database
if (userId != null) {
var dbExams = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getExams(userId: userId);
_exams = dbExams;
notifyListeners();
}
}
// Fetches Exams from the Kreta API then stores them in the database
Future<void> fetch() async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot fetch Exams for User null";
String iss = user.instituteCode;
List? examsJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.exams(iss));
if (examsJson == null) throw "Cannot fetch Exams for User ${user.id}";
List<Exam> exams = examsJson.map((e) => Exam.fromJson(e)).toList();
if (exams.isNotEmpty || _exams.isNotEmpty) await store(exams);
}
// Stores Exams in the database
Future<void> store(List<Exam> exams) async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot store Exams for User null";
String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeExams(exams, userId: userId);
_exams = exams;
notifyListeners();
}
}

View File

@@ -0,0 +1,134 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/grade.dart';
import 'package:filcnaplo_kreta_api/models/group_average.dart';
import 'package:flutter/material.dart';
class GradeProvider with ChangeNotifier {
// Private
late List<Grade> _grades;
late DateTime _lastSeen;
late String _groups;
List<GroupAverage> _groupAvg = [];
late final SettingsProvider _settings;
late final UserProvider _user;
late final DatabaseProvider _database;
late final KretaClient _kreta;
// Public
List<Grade> get grades => _grades;
DateTime get lastSeenDate => _settings.gradeOpeningFun ? _lastSeen : DateTime(3000);
String get groups => _groups;
List<GroupAverage> get groupAverages => _groupAvg;
GradeProvider({
List<Grade> initialGrades = const [],
required SettingsProvider settings,
required UserProvider user,
required DatabaseProvider database,
required KretaClient kreta,
}) {
_settings = settings;
_user = user;
_database = database;
_kreta = kreta;
_grades = List.castFrom(initialGrades);
_lastSeen = DateTime.now();
if (_grades.isEmpty) restore();
}
Future<void> seenAll() async {
String? userId = _user.id;
if (userId != null) {
final userStore = _database.userStore;
userStore.storeLastSeenGrade(DateTime.now(), userId: userId);
_lastSeen = DateTime.now();
notifyListeners();
}
}
Future<void> restore() async {
String? userId = _user.id;
// Load grades from the database
if (userId != null) {
final userQuery = _database.userQuery;
_grades = await userQuery.getGrades(userId: userId);
await convertBySettings();
_groupAvg = await userQuery.getGroupAverages(userId: userId);
notifyListeners();
DateTime lastSeenDB = await userQuery.lastSeenGrade(userId: userId);
if (lastSeenDB.millisecondsSinceEpoch == 0 || lastSeenDB.year == 0 || !_settings.gradeOpeningFun) {
_lastSeen = DateTime.now();
await seenAll();
} else {
_lastSeen = lastSeenDB;
}
notifyListeners();
}
}
// good student mode, renamed subjects
Future<void> convertBySettings() async {
Map<String, String> renamedSubjects = _settings.renamedSubjectsEnabled ? await _database.userQuery.renamedSubjects(userId: _user.user!.id) : {};
for (Grade grade in _grades) {
grade.subject.renamedTo = renamedSubjects.isNotEmpty ? renamedSubjects[grade.subject.id] : null;
grade.value.value = _settings.goodStudent ? 5 : grade.json!["SzamErtek"] ?? 0;
grade.value.valueName = _settings.goodStudent ? "Példás" : grade.json!["SzovegesErtek"] ?? "";
grade.value.shortName = _settings.goodStudent ? "Példás" : grade.json!["SzovegesErtekelesRovidNev"] ?? "";
}
notifyListeners();
}
// Fetches Grades from the Kreta API then stores them in the database
Future<void> fetch() async {
User? user = _user.user;
if (user == null) throw "Cannot fetch Grades for User null";
String iss = user.instituteCode;
List? gradesJson = await _kreta.getAPI(KretaAPI.grades(iss));
if (gradesJson == null) throw "Cannot fetch Grades for User ${user.id}";
List<Grade> grades = gradesJson.map((e) => Grade.fromJson(e)).toList();
if (grades.isNotEmpty || _grades.isNotEmpty) await store(grades);
List? groupsJson = await _kreta.getAPI(KretaAPI.groups(iss));
if (groupsJson == null || groupsJson.isEmpty) throw "Cannot fetch Groups for User ${user.id}";
_groups = (groupsJson[0]["OktatasNevelesiFeladat"] ?? {})["Uid"] ?? "";
List? groupAvgJson = await _kreta.getAPI(KretaAPI.groupAverages(iss, _groups));
if (groupAvgJson == null) throw "Cannot fetch Class Averages for User ${user.id}";
final groupAvgs = groupAvgJson.map((e) => GroupAverage.fromJson(e)).toList();
await storeGroupAvg(groupAvgs);
}
// Stores Grades in the database
Future<void> store(List<Grade> grades) async {
User? user = _user.user;
if (user == null) throw "Cannot store Grades for User null";
String userId = user.id;
await _database.userStore.storeGrades(grades, userId: userId);
_grades = grades;
await convertBySettings();
}
Future<void> storeGroupAvg(List<GroupAverage> groupAvgs) async {
_groupAvg = groupAvgs;
User? user = _user.user;
if (user == null) throw "Cannot store Grades for User null";
String userId = user.id;
await _database.userStore.storeGroupAverages(groupAvgs, userId: userId);
notifyListeners();
}
}

View File

@@ -0,0 +1,65 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/homework.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HomeworkProvider with ChangeNotifier {
late List<Homework> _homework;
late BuildContext _context;
List<Homework> get homework => _homework;
HomeworkProvider({
List<Homework> initialHomework = const [],
required BuildContext context,
}) {
_homework = List.castFrom(initialHomework);
_context = context;
if (_homework.isEmpty) restore();
}
Future<void> restore() async {
String? userId = Provider.of<UserProvider>(_context, listen: false).id;
// Load homework from the database
if (userId != null) {
var dbHomework = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getHomework(userId: userId);
_homework = dbHomework;
notifyListeners();
}
}
// Fetches Homework from the Kreta API then stores them in the database
Future<void> fetch({DateTime? from, bool db = true}) async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot fetch Homework for User null";
String iss = user.instituteCode;
List? homeworkJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.homework(iss, start: from));
if (homeworkJson == null) throw "Cannot fetch Homework for User ${user.id}";
List<Homework> homework = [];
await Future.forEach(homeworkJson.cast<Map>(), (Map hw) async {
Map? e = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.homework(iss, id: hw["Uid"]));
if (e != null) homework.add(Homework.fromJson(e));
});
if (homework.isEmpty && _homework.isEmpty) return;
if (db) await store(homework);
_homework = homework;
notifyListeners();
}
// Stores Homework in the database
Future<void> store(List<Homework> homework) async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot store Homework for User null";
String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeHomework(homework, userId: userId);
}
}

View File

@@ -0,0 +1,80 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/message.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MessageProvider with ChangeNotifier {
late List<Message> _messages;
late BuildContext _context;
List<Message> get messages => _messages;
MessageProvider({
List<Message> initialMessages = const [],
required BuildContext context,
}) {
_messages = List.castFrom(initialMessages);
_context = context;
if (_messages.isEmpty) restore();
}
Future<void> restore() async {
String? userId = Provider.of<UserProvider>(_context, listen: false).id;
// Load messages from the database
if (userId != null) {
var dbMessages = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getMessages(userId: userId);
_messages = dbMessages;
notifyListeners();
}
}
// Fetches all types of Messages
Future<void> fetchAll() => Future.forEach(MessageType.values, (MessageType v) => fetch(type: v));
// Fetches Messages from the Kreta API then stores them in the database
Future<void> fetch({MessageType type = MessageType.inbox}) async {
// Check Message Type
if (type == MessageType.draft) return;
String messageType = ["beerkezett", "elkuldott", "torolt"].elementAt(type.index);
// Check User
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot fetch Messages for User null";
// Get messages
List? messagesJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.messages(messageType));
if (messagesJson == null) throw "Cannot fetch Messages for User ${user.id}";
// Parse messages
List<Message> messages = [];
await Future.wait(List.generate(messagesJson.length, (index) {
return () async {
Map message = messagesJson.cast<Map>()[index];
Map? messageJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.message(message["azonosito"].toString()));
if (messageJson != null) messages.add(Message.fromJson(messageJson, forceType: type));
}();
}));
await store(messages, type);
}
// Stores Messages in the database
Future<void> store(List<Message> messages, MessageType type) async {
// Only store the specified type
_messages.removeWhere((m) => m.type == type);
_messages.addAll(messages);
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot store Messages for User null";
String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeMessages(_messages, userId: userId);
notifyListeners();
}
}

View File

@@ -0,0 +1,59 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/note.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class NoteProvider with ChangeNotifier {
late List<Note> _notes;
late BuildContext _context;
List<Note> get notes => _notes;
NoteProvider({
List<Note> initialNotes = const [],
required BuildContext context,
}) {
_notes = List.castFrom(initialNotes);
_context = context;
if (_notes.isEmpty) restore();
}
Future<void> restore() async {
String? userId = Provider.of<UserProvider>(_context, listen: false).id;
// Load notes from the database
if (userId != null) {
var dbNotes = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getNotes(userId: userId);
_notes = dbNotes;
notifyListeners();
}
}
// Fetches Notes from the Kreta API then stores them in the database
Future<void> fetch() async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot fetch Notes for User null";
String iss = user.instituteCode;
List? notesJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.notes(iss));
if (notesJson == null) throw "Cannot fetch Notes for User ${user.id}";
List<Note> notes = notesJson.map((e) => Note.fromJson(e)).toList();
if (notes.isNotEmpty || _notes.isNotEmpty) await store(notes);
}
// Stores Notes in the database
Future<void> store(List<Note> notes) async {
User? user = Provider.of<UserProvider>(_context, listen: false).user;
if (user == null) throw "Cannot store Notes for User null";
String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeNotes(notes, userId: userId);
_notes = notes;
notifyListeners();
}
}

View File

@@ -0,0 +1,92 @@
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:flutter/material.dart';
class TimetableProvider with ChangeNotifier {
Map<Week, List<Lesson>> _lessons = {};
late final UserProvider _user;
late final DatabaseProvider _database;
late final KretaClient _kreta;
TimetableProvider({
required UserProvider user,
required DatabaseProvider database,
required KretaClient kreta,
}) : _user = user,
_database = database,
_kreta = kreta {
restoreUser();
}
Future<void> restoreUser() async {
String? userId = _user.id;
// Load lessons from the database
if (userId != null) {
var dbLessons = await _database.userQuery.getLessons(userId: userId);
_lessons = dbLessons;
await convertBySettings();
}
}
// for renamed subjects
Future<void> convertBySettings() async {
Map<String, String> renamedSubjects =
(await _database.query.getSettings(_database)).renamedSubjectsEnabled ? await _database.userQuery.renamedSubjects(userId: _user.id!) : {};
for (Lesson lesson in _lessons.values.expand((e) => e)) {
lesson.subject.renamedTo = renamedSubjects.isNotEmpty ? renamedSubjects[lesson.subject.id] : null;
}
notifyListeners();
}
List<Lesson>? getWeek(Week week) => _lessons[week];
// Fetches Lessons from the Kreta API then stores them in the database
Future<void> fetch({Week? week}) async {
if (week == null) return;
User? user = _user.user;
if (user == null) throw "Cannot fetch Lessons for User null";
String iss = user.instituteCode;
List? lessonsJson = await _kreta.getAPI(KretaAPI.timetable(iss, start: week.start, end: week.end));
if (lessonsJson == null) throw "Cannot fetch Lessons for User ${user.id}";
List<Lesson> lessons = lessonsJson.map((e) => Lesson.fromJson(e)).toList();
if (lessons.isEmpty && _lessons.isEmpty) return;
_lessons[week] = lessons;
await store();
await convertBySettings();
}
// Stores Lessons in the database
Future<void> store() async {
User? user = _user.user;
if (user == null) throw "Cannot store Lessons for User null";
String userId = user.id;
// TODO: clear indexes with weeks outside of the current school year
await _database.userStore.storeLessons(_lessons, userId: userId);
}
// Future<void> setLessonCount(SubjectLessonCount lessonCount, {bool store = true}) async {
// _subjectLessonCount = lessonCount;
// if (store) {
// User? user = Provider.of<UserProvider>(_context, listen: false).user;
// if (user == null) throw "Cannot store Lesson Count for User null";
// String userId = user.id;
// await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeSubjectLessonCount(lessonCount, userId: userId);
// }
// notifyListeners();
// }
}

View File

@@ -0,0 +1,20 @@
name: filcnaplo_kreta_api
publish_to: "none"
environment:
sdk: ">=2.17.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
filcnaplo:
path: ../filcnaplo/
http: ^0.13.3
provider: ^5.0.0
file_picker:
git:
url: https://github.com/filc/flutter_file_picker.git
ref: master
dev_dependencies:
flutter_lints: ^1.0.0