Skip to content

Commit

Permalink
added readme an some other minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandDaum committed Jul 31, 2024
1 parent 60b2fa5 commit ebc92de
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 236 deletions.
96 changes: 96 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# GBE VPlan

![Project Banner](https://github.com/RolandDaum/gbevplan/blob/main/docs/gbevplan_banner_borderradius.png?raw=true)

A mobile app made with Flutter and Firebase to display your timetable and get in app access to the school Substitution plan. (GBE only)

## Table of Contents


- [Table of Contents](#table-of-contents)
- [Introduction / Storyline](#introduction--storyline)
- [Features](#features)
- [Installation](#installation)
- [Under the hood](#under-the-hood)
- [Current state \& Future plans](#current-state--future-plans)


## Introduction / Storyline

###### Gymnasium Bad Essen - Vertretungsplan

GBE VPlan was an idea me and a friend of mine had a couple of years ago. The plan was simple. Take the current boring substitution plan, web scraping all the data out of it and display it in a more convenient modern looking way. Unfortunately we never really started to work on this project.

Now, almost 2 years later, I discovered Flutter, an awesome Crossplattform development Framework, primary for building mobile apps. Soon I started to try it out and build my [first prototype](/RolandDaum/gbevplan/tree/OUTDATED-w11design) of the app in a Windows 11 styled App design. Due to school I stopped working on the project and complete restarted last summer holiday, now using the already existing Google Material 3 Widget Component library. After a few weeks of continues development I am at a state which is quite far from the original Idea but still a useful tool for a student and with potential an infinite long list of features that could be added to it.

## Features

- Easy access to your daily timetable with a clean layout and clearly recognizable structure

- Automatic weekday selection

- Automatic selection of the current lesson

- There isn't more than you need

- Easy in app access to the original schools Substitution plan

- Get notified as soon as a new Substitution plan has been released (max. 1h later)

- Edit your lessons quickly and never think about when you have which subject

- Change the overall app theme colour just the way you like

- Possible thanks to Material's ThemeData Generation via seed colour

- Switch between dark/light/system mode with the click of a button

## Installation

If you want to take advantage of the app or just want to try it out, head over to the GitHub release section and download and install the [latest version](https://github.com/RolandDaum/gbevplan/releases/latest).

From there on the app should guide you just fine through the setup process, and you shouldn't have any problem with it, if you're going to the GBE.

Unfortunately due to apples requirement to use a Mac with any kind of m1, m2, m3 CPU to compile and deploy IOS apps, I'm not able to generate an IOS build. Therefore, you will only be able to use the app on Android for the time being.

## Under the hood

The entire app is written in dart with the use of the Flutter Framework. All the non client sided storage and fetching the UUIDs of the original Substitution plan is done in Firebase.

Firebase is a Google Dev-Kit for mobile and web Applications. In my case I used Firestore to store the different Timetables for each grade and the real-time database for quick repetitive, but necessary data access. Firebase also allows you to send push notification which I took use of to notify the user when a new substitution plan has been released. The fourth and final tool from Firebase I used are the cloud functions. You're basically writing a JS or TS script and let it run on different triggers, e.g. on schedule or on change in the Database.

## Current state & Future plans

Right now the app is just working fine for all grades which timetable is based on a course basis. The functionality of display lower grades that have fixed school classes with shared lessons is not implemented. In addition, of missing data I'm also only capable to provide service for my own grade and no one else (sorry).

Also, the original idea of calculating your timetable with the changes of the Substitution plan has not been implemented due to lag of time. No doubt it would be awesome to have this, but it is quite some work to fetch and process the data from the HTML table correct, so that no wrong or misleading information about lessons or the timetable are displayed.

###### Features to be implemented

- More settings

- Notifications

- en/disable

- more detailed notifications

- Option to use System Theme as the seed colour for the app

- Substitution plan calculation

- Proper webscraing and processing of the HTML table (Firebase Cloud Functions)

- Stats page

- See how many lessons are left on the day, week, month, school year

- Something like a school streak (How many days in a row did I go to school without skipping any course)

- Stats of the most visited courses (only makes sense in higher grades)

- Admin app

- Quick Access to change stuff in the Firebase database or real-time db

- e.g. there is a wrong room entry for a lesson
Binary file added docs/gbevplan_banner_borderradius.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions gbevplan/README.md

This file was deleted.

3 changes: 2 additions & 1 deletion gbevplan/lib/Objects/timetable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Timetable {
Timetable({required this.timetable});

/// Creates a new timetable based on the user selected courses and the in the firestore saved global timetable
static createTimetable() async {
static Future<void> createTimetable() async {
Box appdataBox = Hive.box("appdata");
int jahrgang = appdataBox.get("jahrgang");
List<String> selectedCourses =
Expand Down Expand Up @@ -82,6 +82,7 @@ class Timetable {
.colorScheme
.onPrimaryContainer,
);
return;
});
}

Expand Down
104 changes: 67 additions & 37 deletions gbevplan/lib/components/course_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ class CourseSelection extends StatefulWidget {
State<CourseSelection> createState() => _CourseSelectionState();
}

class _CourseSelectionState extends State<CourseSelection> {
class _CourseSelectionState extends State<CourseSelection>
with WidgetsBindingObserver {
TextEditingController tecKursSelection = TextEditingController();
Box appdataBox = Hive.box("appdata");
int jahrgang = 0;
List<String> allekurse = [];
List<String> nonselectedKurse = [];
List<String> selectedKurse = [];
FocusNode dropdownFocusNode = FocusNode();
double bottomInsetD1 = -1;
double bottomInsetD2 = -1;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);

jahrgang = appdataBox.get("jahrgang");
if (!(jahrgang >= 5)) {
Expand Down Expand Up @@ -63,24 +67,53 @@ class _CourseSelectionState extends State<CourseSelection> {
}
}

@override
void didChangeMetrics() {
super.didChangeMetrics();

// Detects the bottom inset delta value to determine if the keyboard is either opening (+ delta) or closing (- delta) in order to unfocus the DropdownMenu so it properly disposes
final bottomInset = View.of(context).viewInsets.bottom;

if (bottomInsetD1 == -1) {
bottomInsetD1 = bottomInset;
} else {
bottomInsetD2 = bottomInset;
double deltaBottomInset = bottomInsetD2 - bottomInsetD1;
if (deltaBottomInset < 0) {
dropdownFocusNode.unfocus();
}
bottomInsetD1 = bottomInsetD2;
bottomInsetD2 = -1;
}
}

@override
void didUpdateWidget(covariant CourseSelection oldWidget) {
super.didUpdateWidget(oldWidget);
calcNonSelectedKurse();
}

void calcNonSelectedKurse() {
selectedKurse = [];
if (widget.selectedKurse.isEmpty) {
selectedKurse = appdataBox.get("selectedKurse").cast<String>();
selectedKurse.addAll(appdataBox.get("selectedKurse").cast<String>());
} else {
selectedKurse = widget.selectedKurse;
selectedKurse.addAll(widget.selectedKurse);
}
nonselectedKurse = [];
nonselectedKurse = allekurse;
nonselectedKurse.addAll(allekurse);
for (var kurs in selectedKurse) {
nonselectedKurse.remove(kurs);
setState(() {
nonselectedKurse.remove(kurs);
});
}
setState(() {});
appdataBox.put("selectedKurse", selectedKurse);
}

@override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);

tecKursSelection.dispose();

Expand All @@ -92,38 +125,35 @@ class _CourseSelectionState extends State<CourseSelection> {

@override
Widget build(BuildContext context) {
return TapRegion(
onTapOutside: (value) {
// TODO: Not optimal -> unfocusing on dropdown list selection -> but works, so the DropdownMenu is getting disposed because for that it has to be unfocused
dropdownFocusNode.unfocus();
return DropdownMenu(
focusNode: dropdownFocusNode,
dropdownMenuEntries:
nonselectedKurse.map<DropdownMenuEntry<String>>((String value) {
return DropdownMenuEntry<String>(
value: value,
label: value,
);
}).toList(),
width: 250,
menuHeight: 200,
controller: tecKursSelection,
label: const Text("Kurse"),
enableSearch: true,
enableFilter: true,
requestFocusOnTap: true,
onSelected: (value) {
setState(() {
if (value == null) {
return;
}
nonselectedKurse.remove(value);
selectedKurse.add(value);
if (widget.onSelectionChange != null) {
widget.onSelectionChange!(selectedKurse);
}
tecKursSelection.text = "";
});
},
child: DropdownMenu(
focusNode: dropdownFocusNode,
dropdownMenuEntries:
nonselectedKurse.map<DropdownMenuEntry<String>>((String value) {
return DropdownMenuEntry<String>(
value: value,
label: value,
);
}).toList(),
width: 250,
menuHeight: 200,
controller: tecKursSelection,
label: const Text("Kurse"),
enableSearch: true,
enableFilter: true,
requestFocusOnTap: true,
onSelected: (value) {
setState(() {
nonselectedKurse.remove(value);
selectedKurse.add(value!);
if (widget.onSelectionChange != null) {
widget.onSelectionChange!(selectedKurse);
}
tecKursSelection.text = "";
});
},
),
);
}
}
Loading

0 comments on commit ebc92de

Please sign in to comment.