diff --git a/.github/workflows/build-patch.yml b/.github/workflows/build-patch.yml
index 6022500..81874d8 100644
--- a/.github/workflows/build-patch.yml
+++ b/.github/workflows/build-patch.yml
@@ -23,7 +23,7 @@ jobs:
with:
fetch-depth: 0
- name: Verify Changed files
- uses: tj-actions/changed-files@v37
+ uses: tj-actions/changed-files@v41
id: verify-changed-files
with:
files: |
diff --git a/.github/workflows/build-prerelease-apk.yml b/.github/workflows/build-prerelease-apk.yml
index f21dfda..f2143cc 100644
--- a/.github/workflows/build-prerelease-apk.yml
+++ b/.github/workflows/build-prerelease-apk.yml
@@ -64,7 +64,8 @@ jobs:
commitChange: true
branch: 'build-prerelease'
labels: 'bump'
- message: 'Bump version to ${{ steps.semvers.outputs.patch }}'
+ message: 'Bump version to ${{ steps.semvers.outputs.patch }} [no ci]'
+ createPR: true
description: 'Automatic version bump to ${{ steps.semvers.outputs.patch }} for prerelease build'
- run: flutter pub get
- run: flutter gen-l10n
diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml
index 0d0e1f4..fabee0f 100644
--- a/.github/workflows/build-release-apk.yml
+++ b/.github/workflows/build-release-apk.yml
@@ -64,7 +64,8 @@ jobs:
commitChange: true
branch: 'build-release'
labels: 'bump'
- message: 'Bump version to ${{ steps.semvers.outputs.minor }}'
+ createPR: true
+ message: 'Bump version to ${{ steps.semvers.outputs.minor }} [no ci]'
description: 'Automatic version bump to ${{ steps.semvers.outputs.minor }} for release build'
- run: flutter pub get
- run: flutter gen-l10n
diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml
index 869c6d3..a567e88 100644
--- a/.github/workflows/test-coverage.yml
+++ b/.github/workflows/test-coverage.yml
@@ -20,10 +20,11 @@ jobs:
with:
fetch-depth: 0
- name: Verify Changed files
- uses: tj-actions/changed-files@v37
+ uses: tj-actions/changed-files@v41
id: verify-changed-files
with:
files: |
+ .github/workflows/test-coverage.yml
pubspec.yaml
pubspec.lock
l10n.yaml
@@ -40,30 +41,6 @@ jobs:
with:
fetch-depth: 0
- - name: Gradle cache
- uses: gradle/gradle-build-action@v2
-
- - name: AVD cache
- uses: actions/cache@v3
- id: avd-cache
- with:
- path: |
- ~/.android/avd/*
- ~/.android/adb*
- key: avd-33
-
- - name: create AVD and generate snapshot for caching
- if: steps.avd-cache.outputs.cache-hit != 'true'
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: 31
- arch: x86_64
- profile: pixel_6_pro
- force-avd-creation: false
- emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- disable-animations: false
- script: echo "Generated AVD snapshot for caching."
-
- uses: actions/setup-java@v2
with:
distribution: 'zulu'
@@ -79,14 +56,16 @@ jobs:
- run: flutter analyze .
- run: flutter gen-l10n
- run: dart pub global run full_coverage
- - run: flutter test --coverage --dart-define=USERNAME=${{ secrets.USERNAME }} --dart-define=PASSWORD=${{ secrets.PASSWORD }}
+ - run: flutter test --coverage
- name: run integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 31
arch: x86_64
profile: pixel_6_pro
+ avd-name: Pixel_6_Pro_API_31
force-avd-creation: false
+ ram-size: 4096M
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: flutter test integration_test/app_test.dart --coverage --dart-define=USERNAME=${{ secrets.USERNAME }} --dart-define=PASSWORD=${{ secrets.PASSWORD }} --dart-define=NAME="${{ secrets.NAME }}"
diff --git a/README.md b/README.md
index 3dffdc3..98fd24d 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,19 @@
-[![build-release-android](https://github.com/DislikesSchool/EduPage2/actions/workflows/build-release-apk.yml/badge.svg)](https://github.com/DislikesSchool/EduPage2/actions/workflows/build-release-apk.yml) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/dislikesschool/edupage2) ![Downloads](https://img.shields.io/github/downloads/DislikesSchool/EduPage2/total) ![Contributors](https://img.shields.io/github/contributors/DislikesSchool/EduPage2?color=dark-green) ![Issues](https://img.shields.io/github/issues/DislikesSchool/EduPage2) ![License](https://img.shields.io/github/license/DislikesSchool/EduPage2) [![codecov](https://codecov.io/github/DislikesSchool/EduPage2/branch/master/graph/badge.svg?token=HKP9WFL0LN)](https://codecov.io/github/DislikesSchool/EduPage2)
+![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/dislikesschool/edupage2) ![Downloads](https://img.shields.io/github/downloads/DislikesSchool/EduPage2/total) ![Contributors](https://img.shields.io/github/contributors/DislikesSchool/EduPage2?color=dark-green) ![Issues](https://img.shields.io/github/issues/DislikesSchool/EduPage2) ![License](https://img.shields.io/github/license/DislikesSchool/EduPage2) [![codecov](https://codecov.io/github/DislikesSchool/EduPage2/branch/master/graph/badge.svg?token=HKP9WFL0LN)](https://codecov.io/github/DislikesSchool/EduPage2)
[![Discord](https://discordapp.com/api/guilds/1143488418840584224/widget.png?style=banner2)](https://discord.gg/xy5nqWa2kQ)
+[![test-coverage](https://github.com/DislikesSchool/EduPage2/actions/workflows/test-coverage.yml/badge.svg)](https://github.com/DislikesSchool/EduPage2/actions/workflows/test-coverage.yml)
+[![build-patch-android](https://github.com/DislikesSchool/EduPage2/actions/workflows/build-patch.yml/badge.svg)](https://github.com/DislikesSchool/EduPage2/actions/workflows/build-patch.yml)
+
## Table Of Contents
- [Table Of Contents](#table-of-contents)
- [About The Project](#about-the-project)
+- [Backend Status](#backend-status)
+ - [Quick status](#quick-status)
+ - [Statuspage](#statuspage)
- [Disclaimer](#disclaimer)
- [Built With](#built-with)
- [Getting Started](#getting-started)
@@ -43,6 +49,19 @@ And that's why we made EduPage2. So far, EduPage2 lacks a pretty big amount of f
EduPage2 uses local caching on your device, and a caching server with our own privte software, which periodically updates data from EduPage, strips it of all useless data (which EduPage includes for some reason), and finally sends out to your device when requested.
+## Backend Status
+
+### Quick status
+
+| Host | Status |
+| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Render.com | [![Better Stack Badge](https://uptime.betterstack.com/status-badges/v1/monitor/w8hv.svg)](https://uptime.betterstack.com/?utm_source=status_badge) |
+| Deta.space | [![Better Stack Badge](https://uptime.betterstack.com/status-badges/v1/monitor/wt8i.svg)](https://uptime.betterstack.com/?utm_source=status_badge) |
+
+### Statuspage
+
+Currently there are two status pages for the EduPage2 backend. The one on [Better Stack](https://ep2.betteruptime.com/) which we have confirmed to work, and the other one on [Statuspage](https://edupage2.statuspage.io/) which seems to work, but we will have to wait unitl an outage occurs to test that.
+
## Disclaimer
**EduPage2** is an open-source project with contributions from multiple individuals and is not affiliated with or endorsed by the creators of EduPage. EduPage is a separate and (possibly) trademarked platform owned by asc Applied Software Consultants, s.r.o.
@@ -55,13 +74,12 @@ This project is open source and distributed under the [GPL-3.0 license](https://
This is a list of all the main tools, libraries and frameworks, that were used in this project
+- [Flutter](https://flutter.dev/)
- [Firebase](https://firebase.google.com/)
- [OneSignal](https://onesignal.com/)
-- [Flutter](https://flutter.dev/)
-- [Express.js](https://expressjs.com/)
-- [PlanetScale](https://planetscale.com/)
-- [Passport.js](https://www.passportjs.org/)
- [Shorebird](https://shorebird.dev/)
+- [Golang](https://go.dev/)
+- [Gin](https://gin-gonic.com/)
## Getting Started
diff --git a/SECURITY.md b/SECURITY.md
index 01eec1a..b5059ec 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,25 +4,29 @@
### App versions
-| Version | API | Supported | Remote patching \* | Changelog |
+| Version | API | Supported | Remote patches \* | Changelog |
| ------------- | --- | ------------------ | ------------------ | ----------------------------------------------------------------------------------- |
-| > 1.7.3 | β13 | :white_check_mark: | :white_check_mark: | [1.7.3...1.7.6](https://github.com/DislikesSchool/EduPage2/compare/v1.7.1...v1.7.3) |
-| 1.7.1 - 1.7.3 | β13 | :white_check_mark: | :x: | [1.7.1...1.7.3](https://github.com/DislikesSchool/EduPage2/compare/v1.7.1...v1.7.3) |
-| 1.7.1 | β13 | :white_check_mark: | :x: | [1.7.0...1.7.1](https://github.com/DislikesSchool/EduPage2/compare/v1.7.0...v1.7.1) |
-| 1.7.0 | β12 | :warning: | :x: | [1.6.0...1.7.0](https://github.com/DislikesSchool/EduPage2/compare/v1.6.0...v1.7.0) |
-| 1.6.0 | β12 | :warning: | :x: | [1.5.2...1.6.0](https://github.com/DislikesSchool/EduPage2/compare/v1.5.2...v1.6.0) |
+| 1.8.2 | v1 | :white_check_mark: | :white_check_mark: | [1.8.0...1.8.2](https://github.com/DislikesSchool/EduPage2/compare/v1.8.0...v1.8.2) |
+| 1.8.0 | β14 | :x: | :white_check_mark: | [1.7.9...1.8.0](https://github.com/DislikesSchool/EduPage2/compare/v1.7.9...v1.8.0) |
+| 1.7.3 - 1.7.9 | β13 | :x: | :white_check_mark: | [1.7.3...1.7.9](https://github.com/DislikesSchool/EduPage2/compare/v1.7.1...v1.7.9) |
+| 1.7.1 - 1.7.3 | β13 | :x: | :x: | [1.7.1...1.7.3](https://github.com/DislikesSchool/EduPage2/compare/v1.7.1...v1.7.3) |
+| 1.7.1 | β13 | :x: | :x: | [1.7.0...1.7.1](https://github.com/DislikesSchool/EduPage2/compare/v1.7.0...v1.7.1) |
+| 1.7.0 | β12 | :x: | :x: | [1.6.0...1.7.0](https://github.com/DislikesSchool/EduPage2/compare/v1.6.0...v1.7.0) |
+| 1.6.0 | β12 | :x: | :x: | [1.5.2...1.6.0](https://github.com/DislikesSchool/EduPage2/compare/v1.5.2...v1.6.0) |
| 1.5.x | β11 | :x: | :x: | [1.5.0...1.5.2](https://github.com/DislikesSchool/EduPage2/compare/v1.5.0...v1.5.2) |
| < 1.5 | | :x: | :x: | |
-\* Remote patching allows us to quickly push patches and bug fixes directly to your device without you having to redownload the app
+\* Remote patches allow us to quickly push patches and bug fixes directly to your device without you having to redownload the app
### API version
| Version | Supported | New features in version |
| ------- | ------------------ | ----------------------- |
-| β13 | :white_check_mark: | |
-| β12 | :warning: | |
-| β11 | :warning: | iCanteen setup |
+| v1 | :white_check_mark: | Complete rewrite |
+| β14 | :x: | |
+| β13 | :x: | |
+| β12 | :x: | |
+| β11 | :x: | iCanteen setup |
| < β11 | :x: | |
:white_check_mark: - Version running on server
diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart
index e173c32..003e93f 100644
--- a/integration_test/app_test.dart
+++ b/integration_test/app_test.dart
@@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:eduapge2/main.dart' as app;
+import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'utils.dart';
@@ -25,20 +26,38 @@ void main() {
await prep(tester, username, password, name);
await tester.tap(find.byType(NavigationDestination).at(1));
- await pumpUntilFound(tester, find.textContaining("Today"));
- expect(find.textContaining("TODAY"), findsOneWidget);
+ await tester.pump(const Duration(seconds: 1));
+ String day = DateFormat('d', const Locale('en', 'US').toString())
+ .format(DateTime.now());
+ String month = DateFormat('MMMM', const Locale('en', 'US').toString())
+ .format(DateTime.now());
+
+ await pumpUntilFound(tester, find.textContaining("$day $month"));
+ expect(find.textContaining("$day $month"), findsWidgets);
});
testWidgets('Test TimeTable page scroll', (tester) async {
await prep(tester, username, password, name);
await tester.tap(find.byType(NavigationDestination).at(1));
- await pumpUntilFound(tester, find.textContaining("Today"));
- expect(find.textContaining("TODAY"), findsOneWidget);
+ await tester.pump(const Duration(seconds: 1));
+ String day = DateFormat('d', const Locale('en', 'US').toString())
+ .format(DateTime.now());
+ String month = DateFormat('MMMM', const Locale('en', 'US').toString())
+ .format(DateTime.now());
+
+ await pumpUntilFound(tester, find.textContaining("$day $month"));
+ expect(find.textContaining("$day $month"), findsWidgets);
await tester.tap(find.byKey(const Key("TimeTableScrollForward")));
- await pumpUntilFound(tester, find.textContaining("Tomorrow"));
- expect(find.textContaining("TOMORROW"), findsOneWidget);
+ await tester.pump(const Duration(seconds: 1));
+ day = DateFormat('d', const Locale('en').toString())
+ .format(DateTime.now().add(const Duration(days: 1)));
+ month = DateFormat('MMMM', const Locale('en').toString())
+ .format(DateTime.now().add(const Duration(days: 1)));
+
+ await pumpUntilFound(tester, find.textContaining("$day $month"));
+ expect(find.textContaining("$day $month"), findsWidgets);
});
});
}
diff --git a/lib/api.dart b/lib/api.dart
new file mode 100644
index 0000000..7edb3cc
--- /dev/null
+++ b/lib/api.dart
@@ -0,0 +1,955 @@
+import 'dart:convert';
+import 'package:dio/dio.dart';
+import 'package:eduapge2/timetable.dart';
+import 'package:firebase_remote_config/firebase_remote_config.dart';
+import 'package:intl/intl.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+Future isConnected() async {
+ var connectivityResult = await (Connectivity().checkConnectivity());
+ if (connectivityResult == ConnectivityResult.mobile) {
+ return true;
+ } else if (connectivityResult == ConnectivityResult.wifi) {
+ return true;
+ }
+ return false;
+}
+
+class EP2Data {
+ final Dio dio = Dio();
+ late SharedPreferences sharedPreferences;
+
+ String baseUrl = "";
+ late User user;
+ late Timeline timeline;
+ late TimeTable timetable;
+
+ static EP2Data? _instance;
+
+ EP2Data._privateConstructor();
+
+ static EP2Data getInstance() {
+ _instance ??= EP2Data._privateConstructor();
+ return _instance!;
+ }
+
+ Future init({
+ required Function(String, double) onProgressUpdate,
+ required AppLocalizations local,
+ }) async {
+ onProgressUpdate(local.loadCredentials, 0.1);
+
+ sharedPreferences = await SharedPreferences.getInstance();
+
+ bool quickstart = sharedPreferences.getBool("quickstart") ?? false;
+
+ String? endpoint = sharedPreferences.getString("customEndpoint");
+ if (endpoint != null && endpoint != "") {
+ baseUrl = endpoint;
+ } else {
+ baseUrl = FirebaseRemoteConfig.instance.getString("testUrl");
+ }
+
+ bool isInternetAvailable = await isConnected();
+
+ user = (await User.loadFromCache()) ??
+ User(
+ username: sharedPreferences.getString("email") ?? "",
+ password: sharedPreferences.getString("password") ?? "",
+ server: sharedPreferences.getString("server") ?? "",
+ );
+
+ if (isInternetAvailable && !quickstart) {
+ if (!await user.validate()) {
+ onProgressUpdate(local.loadLoggingIn, 0.2);
+ if (!await user.login()) {
+ return false;
+ }
+ }
+ }
+ onProgressUpdate(local.loadLoggedIn, 0.4);
+
+ timeline = (await Timeline.loadFromCache()) ??
+ Timeline(
+ homeworks: {},
+ items: {},
+ );
+
+ if (isInternetAvailable && !quickstart) {
+ onProgressUpdate(local.loadDownloadMessages, 0.6);
+ await timeline.loadMessages();
+ }
+
+ timetable = (await TimeTable.loadFromCache()) ?? TimeTable();
+
+ if (isInternetAvailable && !quickstart) {
+ onProgressUpdate(local.loadDownloadTimetable, 0.8);
+ await timetable.loadRecentTt();
+ }
+
+ if (quickstart && isInternetAvailable) {
+ loadInBackground();
+ }
+
+ return true;
+ }
+
+ Future loadInBackground() async {
+ if (!await user.validate()) {
+ await user.login();
+ }
+ await timeline.loadMessages();
+ await timetable.loadRecentTt();
+ }
+}
+
+class User {
+ final EP2Data data = EP2Data.getInstance();
+
+ final String username;
+ final String password;
+ String? server = "";
+
+ String token = "";
+ String name = "";
+
+ User({
+ required this.username,
+ required this.password,
+ this.server,
+ });
+
+ Future login() async {
+ try {
+ Response resp = await data.dio.post(
+ "${data.baseUrl}/login",
+ data: {
+ "username": username,
+ "password": password,
+ "server": server ?? "",
+ },
+ options: Options(contentType: Headers.formUrlEncodedContentType),
+ );
+
+ token = resp.data['token'];
+ name = resp.data["name"];
+
+ saveToCache();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ Future validate() async {
+ try {
+ Response resp = await data.dio.get(
+ "${data.baseUrl}/validate-token",
+ options: Options(headers: {"Authorization": "Bearer $token"}),
+ );
+
+ if (resp.data['success'] != true) {
+ return false;
+ }
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ Map toJson() {
+ return {
+ 'username': username,
+ 'password': password,
+ 'server': server,
+ 'token': token,
+ 'name': name,
+ };
+ }
+
+ factory User.fromJson(Map json) {
+ return User(
+ username: json['username'],
+ password: json['password'],
+ server: json['server'],
+ )
+ ..token = json['token']
+ ..name = json['name'];
+ }
+
+ Future saveToCache() async {
+ final prefs = await SharedPreferences.getInstance();
+ final userJson = jsonEncode(toJson());
+ await prefs.setString('user', userJson);
+ }
+
+ static Future loadFromCache() async {
+ final prefs = await SharedPreferences.getInstance();
+ final userJson = prefs.getString('user');
+ if (userJson != null) {
+ return User.fromJson(jsonDecode(userJson));
+ } else {
+ return null;
+ }
+ }
+}
+
+class TimeTable {
+ final EP2Data data = EP2Data.getInstance();
+
+ final Map timetables = {};
+ List? periods;
+
+ Future> loadPeriods(String token) async {
+ if (periods != null) {
+ return periods!;
+ }
+
+ Response periodsResponse = await data.dio.get(
+ "${data.baseUrl}/api/periods",
+ options: Options(
+ headers: {
+ "Authorization": "Bearer $token",
+ },
+ ),
+ );
+
+ periods = [];
+ for (Map period in periodsResponse.data.values) {
+ periods!.add(TimeTablePeriod(period["id"], period["starttime"],
+ period["endtime"], period["name"], period["short"]));
+ }
+
+ return periods!;
+ }
+
+ Future loadTt(DateTime date) async {
+ DateTime dateOnly = DateTime(date.year, date.month, date.day);
+ if (timetables.containsKey(dateOnly)) {
+ return timetables[dateOnly]!;
+ }
+
+ Response response = await data.dio.get(
+ "${data.baseUrl}/api/timetable?to=${DateFormat('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'en_US').format(DateTime(date.year, date.month, date.day))}&from=${DateFormat('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'en_US').format(DateTime(date.year, date.month, date.day))}",
+ options: Options(
+ headers: {
+ "Authorization": "Bearer ${data.user.token}",
+ },
+ ),
+ );
+
+ List ttClasses = [];
+ Map lessons = response.data["Days"];
+ for (Map ttLesson
+ in lessons.values.isEmpty ? [] : lessons.values.first) {
+ if (ttLesson["studentids"] != null) {
+ ttClasses.add(TimeTableClass.fromJson(ttLesson));
+ }
+ }
+
+ periods = await loadPeriods(data.user.token);
+
+ TimeTableData t = processTimeTable(TimeTableData(
+ DateTime.parse(response.data["Days"].keys.isEmpty
+ ? date.toString()
+ : response.data["Days"].keys.first),
+ ttClasses,
+ periods!));
+
+ timetables[dateOnly] = t;
+ await saveToCache();
+ return t;
+ }
+
+ Future> loadRecentTt() async {
+ Response response = await data.dio.get(
+ "${data.baseUrl}/api/timetable/recent",
+ options: Options(
+ headers: {
+ "Authorization": "Bearer ${data.user.token}",
+ },
+ ),
+ );
+
+ List recentTimetables = [];
+ for (MapEntry day in response.data["Days"].entries) {
+ List ttClasses = [];
+ for (Map ttLesson in day.value) {
+ if (ttLesson["studentids"] != null) {
+ ttClasses.add(TimeTableClass.fromJson(ttLesson));
+ }
+ }
+ periods = await loadPeriods(data.user.token);
+ DateTime date = DateTime.parse(day.key);
+ recentTimetables
+ .add(processTimeTable(TimeTableData(date, ttClasses, periods!)));
+ DateTime dateOnly = DateTime(date.year, date.month, date.day);
+ timetables[dateOnly] = recentTimetables.last;
+ }
+
+ await saveToCache();
+ return recentTimetables;
+ }
+
+ Future today() async {
+ return await loadTt(DateTime.now());
+ }
+
+ Map toJson() => {
+ 'timetables': timetables.map(
+ (key, value) => MapEntry(key.toIso8601String(), value.toJson())),
+ 'periods': periods?.map((p) => p.toJson()).toList(),
+ };
+
+ static TimeTable fromJson(Map json) {
+ return TimeTable()
+ ..timetables.addAll((json['timetables'] as Map).map(
+ (key, value) => MapEntry(
+ DateTime.parse(key),
+ TimeTableData.fromJson(value),
+ ),
+ ))
+ ..periods = (json['periods'] as List)
+ .map((p) => TimeTablePeriod.fromJson(p as Map))
+ .toList();
+ }
+
+ Future saveToCache() async {
+ final prefs = await SharedPreferences.getInstance();
+ prefs.setString('timetable', jsonEncode(toJson()));
+ }
+
+ static Future loadFromCache() async {
+ final prefs = await SharedPreferences.getInstance();
+ if (!prefs.containsKey('timetable')) {
+ return null;
+ }
+ return fromJson(jsonDecode(prefs.getString('timetable')!));
+ }
+}
+
+class TimeTableData {
+ TimeTableData(this.date, this.classes, this.periods);
+
+ final DateTime date;
+ final List classes;
+ final List periods;
+
+ Map toJson() => {
+ 'date': date.toIso8601String(),
+ 'classes': classes.map((c) => c.toJson()).toList(),
+ 'periods': periods.map((p) => p.toJson()).toList(),
+ };
+
+ static TimeTableData fromJson(Map json) =>
+ processTimeTable(TimeTableData(
+ DateTime.parse(json['date']),
+ (json['classes'] as List)
+ .map((c) => TimeTableClass.fromJson(c as Map))
+ .toList(),
+ (json['periods'] as List)
+ .map((p) => TimeTablePeriod.fromJson(p as Map))
+ .toList(),
+ ));
+}
+
+class TimeTablePeriod {
+ final String id;
+ final String startTime;
+ final String endTime;
+ final String name;
+ final String short;
+
+ TimeTablePeriod(this.id, this.startTime, this.endTime, this.name, this.short);
+
+ Map toJson() => {
+ 'id': id,
+ 'starttime': startTime,
+ 'endtime': endTime,
+ 'name': name,
+ 'short': short,
+ };
+
+ static TimeTablePeriod fromJson(Map json) => TimeTablePeriod(
+ json['id'],
+ json['starttime'],
+ json['endtime'],
+ json['name'],
+ json['short'],
+ );
+}
+
+class TimeTableClass {
+ TimeTableClass({
+ this.type = "",
+ this.date = "",
+ required this.period,
+ required this.startTime,
+ required this.endTime,
+ this.subject,
+ this.classes = const [],
+ this.groupNames = const [],
+ this.iGroupId = "",
+ this.teachers = const [],
+ this.classrooms = const [],
+ this.studentIds = const [],
+ this.colors = const [],
+ });
+
+ final String type;
+ final String date;
+ final String period;
+ final String startTime;
+ final String endTime;
+ final Subject? subject;
+ final List classes;
+ final List groupNames;
+ final String iGroupId;
+ final List teachers;
+ final List classrooms;
+ final List studentIds;
+ final List colors;
+ TimeTablePeriod? startPeriod;
+ TimeTablePeriod? endPeriod;
+
+ Map toJson() => {
+ 'type': type,
+ 'date': date,
+ 'uniperiod': period,
+ 'starttime': startTime,
+ 'endtime': endTime,
+ 'subject': subject?.toJson(),
+ 'classes': classes.map((c) => c.toJson()).toList(),
+ 'groupnames': groupNames,
+ 'igroupid': iGroupId,
+ 'teachers': teachers.map((t) => t.toJson()).toList(),
+ 'classrooms': classrooms.map((c) => c.toJson()).toList(),
+ 'studentids': studentIds,
+ 'colors': colors,
+ };
+
+ static TimeTableClass fromJson(Map json) => TimeTableClass(
+ type: json['type'],
+ date: json['date'],
+ period: json['uniperiod'],
+ startTime: json['starttime'],
+ endTime: json['endtime'],
+ subject: Subject.fromJson(json['subject'] ??
+ {"id": "", "name": "", "short": "", "cbhidden": false}),
+ classes: (json['classes'] as List)
+ .map((c) => Class.fromJson(c as Map))
+ .toList(),
+ groupNames: List.from(json['groupnames']),
+ iGroupId: json['igroupid'],
+ teachers: (json['teachers'] as List)
+ .map((t) => Teacher.fromJson(t as Map))
+ .toList(),
+ classrooms: (json['classrooms'] as List)
+ .map((c) => Classroom.fromJson(c as Map))
+ .toList(),
+ studentIds: List.from(json['studentids']),
+ colors: List.from(json['colors'] ?? []),
+ );
+}
+
+class Teacher {
+ Teacher({
+ required this.id,
+ required this.firstName,
+ required this.lastName,
+ required this.short,
+ required this.gender,
+ required this.classroomId,
+ required this.dateFrom,
+ required this.dateTo,
+ required this.isOut,
+ });
+
+ final String id;
+ final String firstName;
+ final String lastName;
+ final String short;
+ final String gender;
+ final String classroomId;
+ final String dateFrom;
+ final String dateTo;
+ final bool isOut;
+
+ Map toJson() => {
+ 'id': id,
+ 'firstname': firstName,
+ 'lastname': lastName,
+ 'short': short,
+ 'gender': gender,
+ 'classroomid': classroomId,
+ 'datefrom': dateFrom,
+ 'dateto': dateTo,
+ 'isout': isOut,
+ };
+
+ static Teacher fromJson(Map json) => Teacher(
+ id: json['id'],
+ firstName: json['firstname'],
+ lastName: json['lastname'],
+ short: json['short'],
+ gender: json['gender'],
+ classroomId: json['classroomid'],
+ dateFrom: json['datefrom'],
+ dateTo: json['dateto'],
+ isOut: json['isout'],
+ );
+}
+
+class Class {
+ Class({
+ required this.id,
+ required this.name,
+ required this.short,
+ required this.grade,
+ required this.teacherId,
+ required this.teacher2Id,
+ required this.classroomId,
+ });
+
+ final String id;
+ final String name;
+ final String short;
+ final String grade;
+ final String teacherId;
+ final String teacher2Id;
+ final String classroomId;
+
+ Map toJson() => {
+ 'id': id,
+ 'name': name,
+ 'short': short,
+ 'grade': grade,
+ 'teacherid': teacherId,
+ 'teacher2id': teacher2Id,
+ 'classroomid': classroomId,
+ };
+
+ static Class fromJson(Map json) => Class(
+ id: json['id'],
+ name: json['name'],
+ short: json['short'],
+ grade: json['grade'],
+ teacherId: json['teacherid'],
+ teacher2Id: json['teacher2id'],
+ classroomId: json['classroomid'],
+ );
+}
+
+class Subject {
+ Subject({
+ required this.id,
+ required this.name,
+ required this.short,
+ required this.cbHidden,
+ });
+
+ final String id;
+ final String name;
+ final String short;
+ final bool cbHidden;
+
+ Map toJson() => {
+ 'id': id,
+ 'name': name,
+ 'short': short,
+ 'cbhidden': cbHidden,
+ };
+
+ static Subject fromJson(Map json) => Subject(
+ id: json['id'],
+ name: json['name'],
+ short: json['short'],
+ cbHidden: json['cbhidden'],
+ );
+}
+
+class Classroom {
+ Classroom({
+ required this.id,
+ required this.name,
+ required this.short,
+ });
+
+ final String id;
+ final String name;
+ final String short;
+
+ Map toJson() => {
+ 'id': id,
+ 'name': name,
+ 'short': short,
+ };
+
+ static Classroom fromJson(Map json) => Classroom(
+ id: json['id'],
+ name: json['name'],
+ short: json['short'],
+ );
+}
+
+class TimelineItem {
+ final String id;
+ final DateTime timestamp;
+ final String reactionTo;
+ final String type;
+ final String user;
+ final String targetUser;
+ final String userName;
+ final String otherId;
+ final String text;
+ final DateTime timeAdded;
+ final DateTime timeEvent;
+ final Map data;
+ final String owner;
+ final String ownerName;
+ final int reactionCount;
+ final String lastReaction;
+ final String pomocnyZaznam;
+ final num removed;
+ final DateTime timeAddedBTC;
+ final DateTime lastReactionBTC;
+
+ TimelineItem({
+ required this.id,
+ required this.timestamp,
+ required this.reactionTo,
+ required this.type,
+ required this.user,
+ required this.targetUser,
+ required this.userName,
+ required this.otherId,
+ required this.text,
+ required this.timeAdded,
+ required this.timeEvent,
+ required this.data,
+ required this.owner,
+ required this.ownerName,
+ required this.reactionCount,
+ required this.lastReaction,
+ required this.pomocnyZaznam,
+ required this.removed,
+ required this.timeAddedBTC,
+ required this.lastReactionBTC,
+ });
+
+ factory TimelineItem.fromJson(Map json) {
+ return TimelineItem(
+ id: json['timelineid'],
+ timestamp: DateTime.parse(json['timestamp']),
+ reactionTo: json['reakcia_na'],
+ type: json['typ'],
+ user: json['user'],
+ targetUser: json['target_user'],
+ userName: json['user_meno'],
+ otherId: json['ineid'],
+ text: json['text'],
+ timeAdded: DateTime.parse(json['cas_pridania']),
+ timeEvent: DateTime.parse(json['cas_udalosti']),
+ data: Map.from(json['data']),
+ owner: json['vlastnik'],
+ ownerName: json['vlastnik_meno'],
+ reactionCount: json['poct_reakcii'],
+ lastReaction: json['posledna_reakcia'],
+ pomocnyZaznam: json['pomocny_zaznam'],
+ removed: json['removed'],
+ timeAddedBTC: DateTime.parse(json['cas_pridania_btc']),
+ lastReactionBTC: DateTime.parse(json['cas_udalosti_btc']),
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'timelineid': id,
+ 'timestamp': timestamp.toIso8601String(),
+ 'reakcia_na': reactionTo,
+ 'typ': type,
+ 'user': user,
+ 'target_user': targetUser,
+ 'user_meno': userName,
+ 'ineid': otherId,
+ 'text': text,
+ 'cas_pridania': timeAdded.toIso8601String(),
+ 'cas_udalosti': timeEvent.toIso8601String(),
+ 'data': data,
+ 'vlastnik': owner,
+ 'vlastnik_meno': ownerName,
+ 'poct_reakcii': reactionCount,
+ 'posledna_reakcia': lastReaction,
+ 'pomocny_zaznam': pomocnyZaznam,
+ 'removed': removed,
+ 'cas_pridania_btc': timeAddedBTC.toIso8601String(),
+ 'cas_udalosti_btc': lastReactionBTC.toIso8601String(),
+ };
+ }
+}
+
+class Homework {
+ final String id;
+ final String homeworkId;
+ final String eSuperId;
+ final String userId;
+ final num lessonId;
+ final String planId;
+ final String name;
+ final String details;
+ final String dateTo;
+ final String dateFrom;
+ final String datetimeTo;
+ final String datetimeFrom;
+ final String dateCreated;
+ final dynamic period;
+ final String timestamp;
+ final String testId;
+ final String type;
+ final num likeCount;
+ final num reactionCount;
+ final num doneCount;
+ final String state;
+ final String lastResult;
+ final List groups;
+ final int eTestCards;
+ final int eTestAnswerCards;
+ final bool studyTopics;
+ final dynamic gradeEventId;
+ final String studentsHidden;
+ final Map data;
+ final String evaluationStatus;
+ final dynamic ended;
+ final bool missingNextLesson;
+ final dynamic attachments;
+ final String authorName;
+ final String lessonName;
+
+ Homework({
+ required this.id,
+ required this.homeworkId,
+ required this.eSuperId,
+ required this.userId,
+ required this.lessonId,
+ required this.planId,
+ required this.name,
+ required this.details,
+ required this.dateTo,
+ required this.dateFrom,
+ required this.datetimeTo,
+ required this.datetimeFrom,
+ required this.dateCreated,
+ required this.period,
+ required this.timestamp,
+ required this.testId,
+ required this.type,
+ required this.likeCount,
+ required this.reactionCount,
+ required this.doneCount,
+ required this.state,
+ required this.lastResult,
+ required this.groups,
+ required this.eTestCards,
+ required this.eTestAnswerCards,
+ required this.studyTopics,
+ required this.gradeEventId,
+ required this.studentsHidden,
+ required this.data,
+ required this.evaluationStatus,
+ required this.ended,
+ required this.missingNextLesson,
+ required this.attachments,
+ required this.authorName,
+ required this.lessonName,
+ });
+
+ factory Homework.fromJson(Map json) {
+ return Homework(
+ id: json['hwkid'],
+ homeworkId: json['homeworkid'],
+ eSuperId: json['e_superid'],
+ userId: json['userid'],
+ lessonId: json['predmetid'],
+ planId: json['planid'],
+ name: json['name'],
+ details: json['details'],
+ dateTo: json['dateto'],
+ dateFrom: json['datefrom'],
+ datetimeTo: json['datetimeto'],
+ datetimeFrom: json['datetimefrom'],
+ dateCreated: json['datecreated'],
+ period: json['period'],
+ timestamp: json['timestamp'],
+ testId: json['testid'],
+ type: json['typ'],
+ likeCount: json['pocet_like'],
+ reactionCount: json['pocet_reakcii'],
+ doneCount: json['pocet_done'],
+ state: json['stav'],
+ lastResult: json['posledny_vysledok'],
+ groups: List.from(json['skupiny']),
+ eTestCards: json['etestCards'],
+ eTestAnswerCards: json['etestAnswerCards'],
+ studyTopics: json['studyTopics'],
+ gradeEventId: json['znamky_udalostid'],
+ studentsHidden: json['students_hidden'],
+ data: Map.from(json['data']),
+ evaluationStatus: json['stavhodnotetimelinePathd'],
+ ended: json['skoncil'],
+ missingNextLesson: json['missingNextLesson'],
+ attachments: json['attachements'],
+ authorName: json['autor_meno'],
+ lessonName: json['predmet_meno'],
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'hwkid': id,
+ 'homeworkid': homeworkId,
+ 'e_superid': eSuperId,
+ 'userid': userId,
+ 'predmetid': lessonId,
+ 'planid': planId,
+ 'name': name,
+ 'details': details,
+ 'dateto': dateTo,
+ 'datefrom': dateFrom,
+ 'datetimeto': datetimeTo,
+ 'datetimefrom': datetimeFrom,
+ 'datecreated': dateCreated,
+ 'period': period,
+ 'timestamp': timestamp,
+ 'testid': testId,
+ 'typ': type,
+ 'pocet_like': likeCount,
+ 'pocet_reakcii': reactionCount,
+ 'pocet_done': doneCount,
+ 'stav': state,
+ 'posledny_vysledok': lastResult,
+ 'skupiny': groups,
+ 'etestCards': eTestCards,
+ 'etestAnswerCards': eTestAnswerCards,
+ 'studyTopics': studyTopics,
+ 'znamky_udalostid': gradeEventId,
+ 'students_hidden': studentsHidden,
+ 'data': data,
+ 'stavhodnotetimelinePathd': evaluationStatus,
+ 'skoncil': ended,
+ 'missingNextLesson': missingNextLesson,
+ 'attachements': attachments,
+ 'autor_meno': authorName,
+ 'predmet_meno': lessonName,
+ };
+ }
+}
+
+class Timeline {
+ EP2Data data = EP2Data.getInstance();
+
+ Map homeworks;
+ Map items;
+
+ Timeline({
+ required this.homeworks,
+ required this.items,
+ });
+
+ factory Timeline.fromJson(Map json) {
+ return Timeline(
+ homeworks: (json['Homeworks'] as Map).map(
+ (key, value) => MapEntry(key, Homework.fromJson(value)),
+ ),
+ items: (json['Items'] as Map).map(
+ (key, value) => MapEntry(key, TimelineItem.fromJson(value)),
+ ),
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'Homeworks': homeworks.map((key, value) => MapEntry(key, value.toJson())),
+ 'Items': items.map((key, value) => MapEntry(key, value.toJson())),
+ };
+ }
+
+ Future saveToCache() async {
+ final timelineJson = jsonEncode(toJson());
+ await data.sharedPreferences.setString('timeline', timelineJson);
+ }
+
+ static Future loadFromCache() async {
+ final prefs = await SharedPreferences.getInstance();
+ final timelineJson = prefs.getString('timeline');
+ if (timelineJson != null) {
+ return Timeline.fromJson(jsonDecode(timelineJson));
+ } else {
+ return null;
+ }
+ }
+
+ Future loadMessages() async {
+ Response response = await data.dio.get(
+ "${data.baseUrl}/api/timeline/recent",
+ options: Options(
+ headers: {
+ "Authorization": "Bearer ${data.user.token}",
+ },
+ ),
+ );
+
+ Map newHomeworks = response.data["Homeworks"];
+ Map newItems = response.data["Items"];
+
+ newHomeworks.forEach((key, value) {
+ homeworks[key] = Homework.fromJson(value);
+ });
+
+ newItems.forEach((key, value) {
+ items[key] = TimelineItem.fromJson(value);
+ });
+
+ await saveToCache();
+ }
+
+ Future loadOlderMessages() async {
+ DateTime oldestTimestamp =
+ items.values.fold(DateTime.now(), (oldest, item) {
+ DateTime timestamp = item.timestamp;
+ return timestamp.isBefore(oldest) ? timestamp : oldest;
+ });
+
+ // Calculate from and to dates
+ DateTime from = oldestTimestamp.subtract(const Duration(days: 14));
+ DateTime to = oldestTimestamp;
+
+ // Add query parameters for from and to dates
+ Response response = await data.dio.get(
+ "${data.baseUrl}/api/timeline",
+ queryParameters: {
+ "from": from.toIso8601String(),
+ "to": to.toIso8601String(),
+ },
+ options: Options(
+ headers: {
+ "Authorization": "Bearer ${data.user.token}",
+ },
+ ),
+ );
+
+ Map newHomeworks = response.data["Homeworks"];
+ Map newItems = response.data["Items"];
+
+ newHomeworks.forEach((key, value) {
+ homeworks[key] = Homework.fromJson(value);
+ });
+
+ newItems.forEach((key, value) {
+ items[key] = TimelineItem.fromJson(value);
+ });
+
+ await saveToCache();
+ }
+}
diff --git a/lib/home.dart b/lib/home.dart
index 1bd8792..4da371c 100644
--- a/lib/home.dart
+++ b/lib/home.dart
@@ -2,19 +2,16 @@ import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
-import 'package:dio_http_cache/dio_http_cache.dart';
+import 'package:eduapge2/api.dart';
import 'package:eduapge2/icanteen_setup.dart';
import 'package:eduapge2/message.dart';
import 'package:eduapge2/messages.dart';
-import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_session_manager/flutter_session_manager.dart';
-import 'package:package_info/package_info.dart';
-import 'package:restart_app/restart_app.dart';
+import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:shorebird_code_push/shorebird_code_push.dart';
import 'package:url_launcher/url_launcher.dart';
class HomePage extends StatefulWidget {
@@ -74,6 +71,11 @@ extension TimeOfDayExtension on TimeOfDay {
return false;
}
}
+
+ static TimeOfDay fromString(String timeString) {
+ List split = timeString.split(':');
+ return TimeOfDay(hour: int.parse(split[0]), minute: int.parse(split[1]));
+ }
}
extension DateTimeExtension on DateTime {
@@ -88,7 +90,8 @@ extension DateTimeExtension on DateTime {
}
}
-LessonStatus getLessonStatus(List lessons, TimeOfDay currentTime) {
+LessonStatus getLessonStatus(
+ List lessons, TimeOfDay currentTime) {
// Check if the user has any lessons today
final hasLessonsToday = lessons.isNotEmpty;
@@ -96,9 +99,9 @@ LessonStatus getLessonStatus(List lessons, TimeOfDay currentTime) {
final hasLesson = hasLessonsToday &&
lessons.any((lesson) {
final startTime = TimeOfDay.fromDateTime(
- DateTimeExtension.parseTime(lesson['period']['startTime']));
- final endTime = TimeOfDay.fromDateTime(
- DateTimeExtension.parseTime(lesson['period']['endTime']));
+ DateTimeExtension.parseTime(lesson.startTime));
+ final endTime =
+ TimeOfDay.fromDateTime(DateTimeExtension.parseTime(lesson.endTime));
return startTime < endTime &&
startTime <= currentTime &&
endTime > currentTime;
@@ -110,23 +113,21 @@ LessonStatus getLessonStatus(List lessons, TimeOfDay currentTime) {
if (hasLesson) {
final currentLesson = lessons.firstWhere((lesson) {
final startTime = TimeOfDay.fromDateTime(
- DateTimeExtension.parseTime(lesson['period']['startTime']));
- final endTime = TimeOfDay.fromDateTime(
- DateTimeExtension.parseTime(lesson['period']['endTime']));
+ DateTimeExtension.parseTime(lesson.startTime));
+ final endTime =
+ TimeOfDay.fromDateTime(DateTimeExtension.parseTime(lesson.endTime));
return startTime < endTime &&
startTime <= currentTime &&
endTime > currentTime;
});
- nextLessonTime =
- DateTimeExtension.parseTime(currentLesson['period']['endTime']);
+ nextLessonTime = DateTimeExtension.parseTime(currentLesson.endTime);
} else if (hasLessonsToday) {
final nextLesson = lessons.firstWhere((lesson) {
final startTime = TimeOfDay.fromDateTime(
- DateTimeExtension.parseTime(lesson['period']['startTime']));
+ DateTimeExtension.parseTime(lesson.startTime));
return startTime > currentTime;
});
- nextLessonTime =
- DateTimeExtension.parseTime(nextLesson['period']['startTime']);
+ nextLessonTime = DateTimeExtension.parseTime(nextLesson.startTime);
} else {
nextLessonTime = DateTime.now();
}
@@ -144,99 +145,25 @@ LessonStatus getLessonStatus(List lessons, TimeOfDay currentTime) {
}
}
-final _shorebirdCodePush = ShorebirdCodePush();
-
class HomePageState extends State {
final GlobalKey scaffoldKey = GlobalKey();
- late SharedPreferences sharedPreferences;
- String baseUrl = FirebaseRemoteConfig.instance.getString("baseUrl");
- late Response response;
- Dio dio = Dio();
-
- bool error = false; //for error status
- bool loading = true; //for data featching status
- String errmsg = ""; //to assing any error message from API/runtime
- dynamic apidata; //for decoded JSON data
- bool refresh = false;
+ SharedPreferences? sharedPreferences;
+
bool updateAvailable = false;
bool quickstart = false;
- bool _isCheckingForUpdate = false;
- late Map apidataTT;
- List apidataMsg = [];
- late String username;
- late LessonStatus _lessonStatus;
+ List apidataMsg = [];
+ String username = "";
+ LessonStatus _lessonStatus = LessonStatus(
+ hasLessonsToday: false, hasLesson: false, nextLessonTime: DateTime.now());
Timer? _timer;
+ TimeTableData t = TimeTableData(DateTime.now(), [], []);
@override
void initState() {
super.initState();
- dio.interceptors
- .add(DioCacheManager(CacheConfig(baseUrl: baseUrl)).interceptor);
+ getData();
fetchAndCompareBuildName();
- getData(); //fetching data
- if (!_isCheckingForUpdate) _checkForUpdate(); // ik that it's not necessary
- }
-
- Future _checkForUpdate() async {
- setState(() {
- _isCheckingForUpdate = true;
- });
-
- // Ask the Shorebird servers if there is a new patch available.
- final isUpdateAvailable =
- await _shorebirdCodePush.isNewPatchAvailableForDownload();
-
- if (!mounted) return;
-
- setState(() {
- _isCheckingForUpdate = false;
- });
-
- if (isUpdateAvailable) {
- _downloadUpdate();
- }
- }
-
- void _showDownloadingBanner() {
- ScaffoldMessenger.of(context).showMaterialBanner(
- const MaterialBanner(
- content: Text('Downloading patch...'),
- actions: [
- SizedBox(
- height: 14,
- width: 14,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- ),
- )
- ],
- ),
- );
- }
-
- void _showRestartBanner() {
- ScaffoldMessenger.of(context).showMaterialBanner(
- const MaterialBanner(
- content: Text('A new patch is ready!'),
- actions: [
- TextButton(
- // Restart the app for the new patch to take effect.
- onPressed: Restart.restartApp,
- child: Text('Restart app'),
- ),
- ],
- ),
- );
- }
-
- Future _downloadUpdate() async {
- _showDownloadingBanner();
- await _shorebirdCodePush.downloadUpdateIfAvailable();
- if (!mounted) return;
-
- ScaffoldMessenger.of(context).hideCurrentMaterialBanner();
- _showRestartBanner();
}
@override
@@ -254,55 +181,24 @@ class HomePageState extends State {
}
getData() async {
- setState(() {
- loading = true;
- });
sharedPreferences = await SharedPreferences.getInstance();
- quickstart = sharedPreferences.getBool('quickstart') ?? false;
- var msgs = await widget.sessionManager.get('messages');
- if (msgs != Null && msgs != null) {
- setState(() {
- apidataMsg = msgs;
- });
- }
+ quickstart = sharedPreferences?.getBool('quickstart') ?? false;
+ apidataMsg = EP2Data.getInstance().timeline.items.values.toList();
+ username = EP2Data.getInstance().user.name;
- Map? user = await widget.sessionManager.get('user');
- if (user == null) {
- apidataTT = {};
- setState(() {
- loading = false;
- });
- return;
- }
- username = user["firstname"] + " " + user["lastname"];
- String token = sharedPreferences.getString("token")!;
-
- Response response = await dio.get(
- "$baseUrl/timetable/${getWeekDay().toString()}",
- options: buildCacheOptions(
- Duration.zero,
- maxStale: const Duration(days: 7),
- options: Options(
- headers: {
- "Authorization": "Bearer $token",
- },
- ),
- ),
- );
- apidataTT = jsonDecode(response.data);
- _lessonStatus = getLessonStatus(apidataTT["lessons"], TimeOfDay.now());
+ t = await EP2Data.getInstance().timetable.today();
+
+ _lessonStatus = getLessonStatus(t.classes, TimeOfDay.now());
if (_lessonStatus.hasLessonsToday) {
_startTimer();
}
- setState(() {
- loading = false;
- }); //refresh UI
+ setState(() {}); //refresh UI
}
void _startTimer() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
setState(() {
- _lessonStatus = getLessonStatus(apidataTT["lessons"], TimeOfDay.now());
+ _lessonStatus = getLessonStatus(t.classes, TimeOfDay.now());
if (!_lessonStatus.hasLessonsToday) {
_timer?.cancel();
}
@@ -356,23 +252,20 @@ class HomePageState extends State {
Widget build(BuildContext context) {
AppLocalizations? local = AppLocalizations.of(context);
ThemeData theme = Theme.of(context);
- if (loading) {
- return Center(
- child: Text(local!.loading),
- );
- }
int lunch = -1;
DateTime orderLunchesFor = DateTime(1998, 4, 10);
- String? l = sharedPreferences.getString("lunches");
+ String? l = sharedPreferences?.getString("lunches");
if (l != null) {
var lunches = jsonDecode(l) as List;
if (lunches.isNotEmpty) {
var lunchToday = lunches[0] as Map;
- lunch = 0;
- var todayLunches = lunchToday["lunches"];
- for (int i = 0; i < todayLunches.length; i++) {
- if (todayLunches[i]["ordered"]) lunch = i + 1;
+ if (DateTime.parse(lunchToday["day"]).day != DateTime.now().day) {
+ lunch = 0;
+ var todayLunches = lunchToday["lunches"];
+ for (int i = 0; i < todayLunches.length; i++) {
+ if (todayLunches[i]["ordered"]) lunch = i + 1;
+ }
}
for (Map li in lunches) {
bool canOrder = false;
@@ -386,22 +279,23 @@ class HomePageState extends State {
}
}
if (canOrder && !hasOrdered) {
- orderLunchesFor = DateTime.parse(li["day"]);
+ DateTime parsed = DateTime.parse(li["day"]);
+ orderLunchesFor = DateTime(parsed.year, parsed.month, parsed.day);
break;
}
}
}
}
- List msgs =
- apidataMsg.where((msg) => msg["type"] == "sprava").toList();
- List msgsWOR = List.from(msgs);
+ List msgs =
+ apidataMsg.where((msg) => msg.type == "sprava").toList();
+ List msgsWOR = List.from(msgs);
List