Merge branch 'dev' into notifications
14
.gitignore
vendored
@ -1,5 +1,7 @@
|
||||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
.gitignore
|
||||
|
||||
termek.txt
|
||||
.DS_Store
|
||||
filc3.properties
|
||||
@ -25,9 +27,6 @@ doc/api/
|
||||
*.js.map
|
||||
|
||||
*.txt
|
||||
filcnaplo/linux/flutter/generated_plugin_registrant.cc
|
||||
filcnaplo/linux/flutter/generated_plugin_registrant.h
|
||||
filcnaplo/linux/flutter/generated_plugins.cmake
|
||||
filcnaplo/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||
filcnaplo/windows/flutter/ephemeral/.plugin_symlinks/connectivity_plus
|
||||
filcnaplo/windows/flutter/ephemeral/.plugin_symlinks/dynamic_color
|
||||
@ -43,10 +42,9 @@ filcnaplo/windows/flutter/generated_plugins.cmake
|
||||
filcnaplo/linux/flutter/generated_plugin_registrant.cc
|
||||
filcnaplo/linux/flutter/generated_plugin_registrant.h
|
||||
filcnaplo/linux/flutter/generated_plugins.cmake
|
||||
filcnaplo/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||
filcnaplo/linux/flutter/generated_plugin_registrant.cc
|
||||
filcnaplo/linux/flutter/generated_plugin_registrant.h
|
||||
filcnaplo/linux/flutter/generated_plugins.cmake
|
||||
filcnaplo/macos/Flutter/GeneratedPluginRegistrant.swift
|
||||
filcnaplo/macos/Flutter/*
|
||||
filcnaplo/ios/Podfile.lock
|
||||
.vscode/
|
||||
key.properties
|
||||
|
||||
.flutter-plugins*
|
||||
|
@ -1,39 +1,36 @@
|
||||
|
||||
# Hozzájárulási útmutató
|
||||
# Contribution guide
|
||||
Köszönjük, ha programozással segíted a munkánkat!
|
||||
A folytatáshoz szükséged lesz egy Linuxot vagy Windowst futtató számítógépre, minimális programozási tapasztalatra és egy kis angoltudásra.
|
||||
Segít, ha nem csak kicsit tudsz programozni, és ha ismered a Gitet és a GitHubot ;)
|
||||
A folytatáshoz szükséged lesz egy Linux-ot vagy Windows-t futtató számítógépre, minimális programozási tapasztalatra és egy kis angoltudásra.
|
||||
Segít, ha már gyakorlottabb vagy a programozásban, és ha ismered a [Git](https://git-scm.com/) és a [GitHub](https://github.com/) működését. ;)
|
||||
|
||||
## Miben segítsek?
|
||||
Kérünk, **olyan dologgal járulj hozzá** a reFilchez, ami valószínűleg **sok embernek hasznos lesz** majd. Szeretnénk egy minél teljeskörűbb iskolai asszisztenst létrehozni, de az iskolaspecifikus, vagy külön neked hasznos funkciók helye inkább legyen a saját forkod.
|
||||
Kérünk, **olyan dologgal járulj hozzá** a **reFilc**hez, ami valószínűleg **sok embernek hasznos lehet**. Szeretnénk egy minél teljeskörűbb iskolai asszisztenst létrehozni, de az iskolaspecifikus, vagy külön neked hasznos funkciók helye inkább legyen a saját Fork-od.
|
||||
|
||||
Fontos, hogy **mielőtt egy nagy volumenű projektbe belekezdesz, futtasd meg ötletedet a [Discord szerverünkön](https://dc.refilc.hu/),** ahol még azelőtt tudunk tanácsot adni, mielőtt sok-sok órát beleöltél volna egy esetleg felesleges dologba.
|
||||
Fontos, hogy **mielőtt egy nagyobb méretű projektbe belekezdenél, futtasd meg ötletedet a [Discord szerverünkön](https://dc.refilc.hu/)**, ahol még azelőtt tudunk tanácsot adni, hogy sok-sok órát beleöltél volna egy esetleg felesleges dologba.
|
||||
|
||||
A legjobban annak örülünk, ha az [Issues](https://github.com/refilc/naplo/issues) oldalról szemezgetsz, **ha lehet, a [help wanted taggel megjelöltekkel kezdd](https://github.com/refilc/naplo/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22),** vagy ha új vagy a Flutterhez, ajánljuk figyelmedbe [ezeket a viszonylag könnyen javítható hibákat](https://github.com/refilc/naplo/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) (ha épp van ilyen).
|
||||
A legjobban annak örülünk, ha az [Issues](https://github.com/refilc/naplo/issues) oldalról szemezgetsz. Ha még új vagy a Flutterben, ajánljuk figyelmedbe ezeket a [viszonylag könnyen javítható hibákat](https://github.com/refilc/naplo/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22), ha éppen van ilyen.
|
||||
|
||||
## Hogyan segítsek?
|
||||
Nem ígérhetünk itt sem programozás-, sem Git-kurzust, de a projektspecifikus dolgokat leírjuk, és segítünk a Flutter telepítésében.
|
||||
|
||||
Nem ígérhetünk itt sem programozás-, sem git-kurzust, de a projektspecifikus dolgokat leírjuk, és segítünk a Flutter feltelepítésében.
|
||||
A **reFilc** a Google által pár éve létrehozott **[Flutter](https://flutter.dev/)** keretrendszert használja, aminek nyelve a **[Dart](https://dart.dev/)**. Ha ismered a C#, Java, C++, vagy egyéb hasonló programnyelvek működését, **nem fog nagy gondot okozni a használata.** A felhasználói felület létrehozásában az is segíthet, ha foglalkoztál már korábban weboldalakkal vagy alkalmazásfejlesztéssel.
|
||||
Ha még nem használtad a Flutter-t, mindenképp böngészd át a [YouTube csatornájukat](https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw).
|
||||
Kód vagy UI teszteléséhez Flutter telepítése nélkül is használhatod a [DartPad](https://dartpad.dev/)-et.
|
||||
|
||||
A reFilc a Google által pár éve létrehozott **[Fluttert](https://flutter.dev/)** használja, aminek nyelve a **[Dart](https://dart.dev/)**. Ha ismered a C#-ot, Javát, C++t, vagy egyéb hasonló nyelvet, **nem fog gondot okozni a használata.** A felhasználói felület létrehozásában az is segíthet, ha foglalkoztál már korábban weboldalakkal.
|
||||
Ha még nem használtál Fluttert, mindenképp böngészd át a [YouTube csatornájukat](https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw).
|
||||
Könnyen tudsz kódot, vagy akár UI-t is tesztelni a [DartPad](https://dartpad.dev/) oldalon.
|
||||
#### [Segítség a Flutter telepítéséhez](https://docs.flutter.dev/get-started/install)
|
||||
**Használd a Flutter stable verzióját!** Írd be a terminálba: `flutter channel stable`
|
||||
|
||||
#### [Segítség a Flutter telepítéséhez és a forráskód futtatásához](https://docs.flutter.dev/get-started/install)
|
||||
Fontos: **Legyél a flutter beta verzióján!** Írd be: `flutter channel beta`
|
||||
Ha nem értessz a Git-hez vagy a GitHub-hoz, ajánljuk figyelmedbe [ezt a cikket](https://medium.com/envienta-magyarorsz%C3%A1g/git-%C3%A9s-github-gyorstalpal%C3%B3-f2d78a732deb), viszont arra kérünk, hogy a használatukat ne a **reFilc**en próbáld ki először. Hozz létre egy saját Repo-t és abban tesztelgess. Ha már nagyjából kitapasztaltad, várjuk hozzájárulásodat.
|
||||
|
||||
|
||||
|
||||
Ha nem értesz a Githez, ajánljuk figyelmedbe [ezt a cikket](https://medium.com/envienta-magyarorsz%C3%A1g/git-%C3%A9s-github-gyorstalpal%C3%B3-f2d78a732deb). Viszont arra kérünk, a Git használatát ne a reFilcen próbáld ki először, hozz létre előbb egy saját Repót, és abba tesztelgess. Ha már nagyjából kitapasztaltad, várjuk hozzájárulásodat.
|
||||
|
||||
Készíts egy forkot a saját fiókod alá.
|
||||
A reFilc legfrissebb, épp fejlesztés alatt álló verzióját a [master branch](https://github.com/refilc/naplo/tree/master)-en találod, kérjük ide commitolj, és ide célozd a forkodból a Pull Requested. Írd le benne, mit változtattál, és ha lehet, csatolj képernyőképet is.
|
||||
Minél gyakrabban készíts minél részletesebben elnevezett commitokat, hogy el tudjunk tájékozódni az általad beküldött kódon.
|
||||
Készíts egy Fork-ot a saját GitHub fiókod alá.
|
||||
A **reFilc** legfrissebb, **épp fejlesztés alatt álló verzióját a [master branch](https://github.com/refilc/naplo/tree/master)-en találod**. Kérjük ide Commit-olj és ide célozd a Fork-odból a Pull Request-edet. Írd le benne, hogy mit változtattál és ha lehet, csatolj képernyőképet is.
|
||||
Minél gyakrabban készíts minél részletesebben elnevezett Commit-okat, hogy mások is el tudjanak igazodni az általad beküldött kódban.
|
||||
|
||||
---
|
||||
|
||||
Az általad fejlesztett funkciók mellé a changelogban odakerül GitHub felhasználóneved.
|
||||
Ha jelentős és rendszeres hozzájáruló vagy, Discordon megkapod a `DEV` rangot.
|
||||
Az általad fejlesztett funkciók mellé a Changelog-ba odakerül a GitHub felhasználóneved.
|
||||
Ha jelentős és rendszeres hozzájáruló vagy, Discord-on megkaphatod a `DEV` rangot.
|
||||
|
||||
Ha bárhol elakadtál, keress minket Discordon.
|
||||
Jó fejlesztést kívánunk!
|
||||
Ha bárhol elakadtál vagy kérdésed van, keress bátran Discordon!
|
||||
**Jó fejlesztést kívánunk!**
|
||||
|
@ -5,14 +5,14 @@
|
||||
|
||||
#### Nem hivatalos e-napló alkalmazás az eKRÉTA rendszerhez - tanulóktól, tanulóknak.
|
||||
|
||||
[](https://github.com/refilc/naplo/releases) [](http://dc.refilc.hu)
|
||||
[](https://github.com/refilc/naplo/releases) [](https://dc.refilc.hu)
|
||||
|
||||
## Setup
|
||||
|
||||
### Clone the project
|
||||
|
||||
```sh
|
||||
git clone --recursive https://github.com/refilc/naplo
|
||||
git clone https://github.com/refilc/naplo
|
||||
cd naplo
|
||||
```
|
||||
|
||||
@ -29,11 +29,13 @@ flutter run
|
||||
|
||||
### Contribution
|
||||
|
||||
**Nézd meg a [Contribution guide](CONTRIBUTING.md)-ot!**
|
||||
|
||||
Az összes (ugyan azon verzióhoz tartozó) contribution meg fog jelenni a release-nél. Kérjük, írd le a Discord nevedet a Description-be, hogy adhassunk rangot.
|
||||
|
||||
-------
|
||||
|
||||
# Kudo
|
||||
# Developers
|
||||
|
||||
**annon:** a Filc napló készítője (ez az app a Filcen alapul)
|
||||
|
||||
|
1
filcnaplo/.gitignore
vendored
@ -44,3 +44,4 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
key.properties
|
||||
|
@ -4,8 +4,8 @@
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
||||
channel: beta
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
|
||||
@ -13,11 +13,11 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
||||
base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
||||
- platform: macos
|
||||
create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
||||
base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: web
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
|
||||
# User provided section
|
||||
|
||||
|
@ -2,12 +2,14 @@
|
||||
xmlns:tools="http://schemas.android.com/tools" package="hu.refilc.naplo">
|
||||
<application android:name="${applicationName}" android:label="reFilc" tools:replace="android:label" android:icon="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity android:exported="true" android:name=".MainActivity"
|
||||
<activity android:exported="true" android:name="hu.refilc.naplo.MainActivity"
|
||||
android:launchMode="singleTop" android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"
|
||||
android:showWhenLocked="true"
|
||||
android:turnScreenOn="true">
|
||||
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background" />
|
||||
<meta-data android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
@ -18,16 +20,26 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Accepts URIs that begin with https://api.filcnaplo.hu -->
|
||||
<!-- Accepts URIs that begin with https://api.refilcapp.hu -->
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="api.filcnaplo.hu"
|
||||
android:pathPrefix="/callback" />
|
||||
android:host="api.refilcapp.hu"
|
||||
android:pathPrefix="/v1/auth/callback" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Accepts URIs that begin with https://refilc.hu -->
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="refilc.hu"
|
||||
android:pathPrefix="/app" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
|
||||
<receiver android:name=".widget_timetable.WidgetTimetable"
|
||||
<receiver android:name="hu.refilc.naplo.widget_timetable.WidgetTimetable"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@ -43,7 +55,7 @@
|
||||
android:resource="@xml/home_widget_test_info" />
|
||||
</receiver>
|
||||
|
||||
<service android:name=".widget_timetable.WidgetTimetableService"
|
||||
<service android:name="hu.refilc.naplo.widget_timetable.WidgetTimetableService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
|
||||
@ -57,11 +69,14 @@
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
|
||||
</application>
|
||||
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
|
||||
<!-- Permissions -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
@ -1 +0,0 @@
|
||||
../../../../../../../../../filcnaplo_premium/android/database
|
@ -1 +0,0 @@
|
||||
../../../../../../../../../filcnaplo_premium/android/utils
|
@ -1 +0,0 @@
|
||||
../../../../../../../../../filcnaplo_premium/android/widget_timetable
|
@ -2,8 +2,6 @@ package hu.refilc.naplo;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
|
||||
}
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:startColor="#123323"
|
||||
android:endColor="#20AC9B"
|
||||
android:angle="135" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/launch_gradient_background" />
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@mipmap/ic_splash"/>
|
||||
</item>
|
||||
</layer-list>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:startColor="#123323"
|
||||
android:endColor="#20AC9B"
|
||||
android:angle="135" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:startColor="#123323"
|
||||
android:endColor="#20AC9B"
|
||||
android:angle="135" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/main_lay"
|
||||
android:layout_height="50dp"
|
||||
@ -15,12 +16,14 @@
|
||||
android:id="@+id/tt_item_num"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:gravity="center"
|
||||
android:text="1."
|
||||
android:textColor="@color/filc"
|
||||
android:textColorLink="#ff3D7BF4"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:text="1."
|
||||
android:gravity="center"
|
||||
android:textColor="@color/filc"></TextView>
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tt_item_name"
|
||||
@ -33,10 +36,11 @@
|
||||
android:text="Óra neve"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:layout_toLeftOf="@id/tt_item_room"
|
||||
android:layout_toStartOf="@id/tt_item_room"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_toRightOf="@id/tt_item_num"
|
||||
android:textColor="@color/text"></TextView>
|
||||
android:layout_toEndOf="@id/tt_item_num"
|
||||
android:textColor="@color/text"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tt_item_name_nodesc"
|
||||
@ -47,13 +51,14 @@
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="2.5dp"
|
||||
android:layout_toLeftOf="@id/tt_item_room"
|
||||
android:layout_toStartOf="@id/tt_item_room"
|
||||
android:text="Óra neve"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_toRightOf="@id/tt_item_num"
|
||||
android:textColor="@color/text"></TextView>
|
||||
android:layout_toEndOf="@id/tt_item_num"
|
||||
android:textColor="@color/text"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tt_item_desc"
|
||||
@ -67,10 +72,11 @@
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_toRightOf="@id/tt_item_num"
|
||||
android:layout_toLeftOf="@id/tt_item_room"
|
||||
android:layout_toEndOf="@id/tt_item_num"
|
||||
android:layout_toStartOf="@id/tt_item_room"
|
||||
android:layout_below="@id/tt_item_name"
|
||||
android:textColor="@color/text_desc"></TextView>
|
||||
android:textColor="@color/text_desc"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tt_item_room"
|
||||
@ -84,8 +90,9 @@
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:gravity="center"
|
||||
android:layout_toLeftOf="@id/tt_item_time"
|
||||
android:textColor="@color/text_desc"></TextView>
|
||||
android:layout_toStartOf="@id/tt_item_time"
|
||||
android:textColor="@color/text_desc"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tt_item_time"
|
||||
@ -95,11 +102,12 @@
|
||||
android:textFontWeight="500"
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:text="8:30\n9:10"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentRight="true"
|
||||
android:textColor="@color/white"></TextView>
|
||||
android:layout_alignParentEnd="true"
|
||||
android:textColor="@color/white"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</RelativeLayout>
|
@ -19,10 +19,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="Empty"
|
||||
android:text="Üres / Empty"
|
||||
android:background="@drawable/widget_card_bottom_dark"
|
||||
android:textColor="@color/text"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/widget_list"
|
||||
@ -43,57 +45,63 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="45dp"
|
||||
android:background="@drawable/widget_card_top_dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_refresh"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toStartOf="@id/nav_to_left"
|
||||
android:clickable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_refresh_cw"
|
||||
android:layout_toLeftOf="@id/nav_to_left"
|
||||
android:tint="@color/text_desc"
|
||||
tools:ignore="UseAppTint"
|
||||
android:padding="10dp" />
|
||||
android:focusable="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_to_left"
|
||||
android:layout_width="50dp"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toStartOf="@id/nav_to_right"
|
||||
android:clickable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_chevron_left"
|
||||
android:layout_toLeftOf="@id/nav_to_right"
|
||||
android:tint="@color/text_desc"
|
||||
tools:ignore="UseAppTint"
|
||||
android:padding="10dp" />
|
||||
android:focusable="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_to_right"
|
||||
android:layout_width="50dp"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:clickable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_chevron_right"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="5dp"
|
||||
android:tint="@color/text_desc"
|
||||
tools:ignore="UseAppTint"
|
||||
android:padding="10dp" />
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nav_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginLeft="20sp"
|
||||
android:layout_toLeftOf="@id/nav_refresh"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="15sp"
|
||||
android:layout_marginTop="2sp"
|
||||
android:layout_toStartOf="@id/nav_refresh"
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:text="Timetable"
|
||||
android:text="Órarend"
|
||||
android:textColor="@color/text"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
@ -131,8 +139,9 @@
|
||||
android:text="A widget használatához, bejelentkezés szükséges."
|
||||
android:textColor="@color/black"
|
||||
android:paddingTop="10dp"
|
||||
android:textSize="17dp"
|
||||
android:textStyle="bold" />
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/open_login"
|
||||
@ -145,8 +154,9 @@
|
||||
android:layout_margin="10dp"
|
||||
android:textColor="@color/white"
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:textSize="16dp"
|
||||
android:textStyle="bold"/>
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@ -185,8 +195,9 @@
|
||||
android:text="Órák a kezdőképernyőd kényelméből."
|
||||
android:textColor="@color/black"
|
||||
android:paddingTop="10dp"
|
||||
android:textSize="16dp"
|
||||
android:textStyle="bold" />
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -197,7 +208,8 @@
|
||||
android:textColor="@color/black"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:textSize="14dp" />
|
||||
android:textSize="14sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buy_premium"
|
||||
@ -211,8 +223,9 @@
|
||||
android:layout_margin="10dp"
|
||||
android:textColor="#ff691A9B"
|
||||
android:fontFamily="@font/montserrat_medium"
|
||||
android:textSize="16dp"
|
||||
android:textStyle="bold"/>
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 11 KiB |
@ -1,12 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||
<item name="android:background">@drawable/app_widget_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
|
||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
||||
</style>
|
||||
</resources>
|
@ -27,7 +27,7 @@
|
||||
<color name="yellow_light">#ffFFCC00</color>
|
||||
<color name="light_yellow_light">#40FFD60A</color>
|
||||
<color name="green_light">#ff34C759</color>
|
||||
<color name="filc_light">#ff247665</color>
|
||||
<color name="filc_light">#ff3D7BF4</color>
|
||||
<color name="teal_light">#ff5AC8FA</color>
|
||||
<color name="blue_light">#ff007AFF</color>
|
||||
<color name="indigo_light">#ff5856D6</color>
|
||||
@ -49,7 +49,7 @@
|
||||
<color name="yellow">#ffFFD60A</color>
|
||||
<color name="light_yellow">#40FFD60A</color>
|
||||
<color name="green">#ff32D74B</color>
|
||||
<color name="filc">#ff29826F</color>
|
||||
<color name="filc">#ff3D7BF4</color>
|
||||
<color name="teal">#ff64D2FF</color>
|
||||
<color name="blue">#ff0A84FF</color>
|
||||
<color name="indigo">#ff5E5CE6</color>
|
||||
|
BIN
filcnaplo/assets/images/card_border.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
filcnaplo/assets/images/subject_covers/math_light.png
Normal file
After Width: | Height: | Size: 19 KiB |
@ -1,3 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
flutter clean
|
||||
dart pub get
|
||||
flutter doctor -v
|
||||
|
||||
flutter build ipa --release --dart-define=APPVER=$(cat pubspec.yaml | grep version: | cut -d' ' -f2 | cut -d+ -f1) --no-tree-shake-icons
|
||||
|
15
filcnaplo/ios/Flutter/Generated 3.xcconfig
Normal file
@ -0,0 +1,15 @@
|
||||
// This is a generated file; do not edit or check into version control.
|
||||
FLUTTER_ROOT=/Users/kima/development/flutter
|
||||
FLUTTER_APPLICATION_PATH=/Users/kima/Documents/refilc/app/naplo/filcnaplo
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_TARGET=/Users/kima/Documents/refilc/app/naplo/filcnaplo/lib/main.dart
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=4.1.0
|
||||
FLUTTER_BUILD_NUMBER=213
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
||||
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
|
||||
DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC8yYTM0MDFjOWJiYjVhOWE5YWVjNzRkNGY3MzVkMThhOWRkM2ViZjJkLw==
|
||||
DART_OBFUSCATION=false
|
||||
TRACK_WIDGET_CREATION=true
|
||||
TREE_SHAKE_ICONS=false
|
||||
PACKAGE_CONFIG=/Users/kima/Documents/refilc/app/naplo/filcnaplo/.dart_tool/package_config.json
|
14
filcnaplo/ios/Flutter/flutter_export_environment 2.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# This is a generated file; do not edit or check into version control.
|
||||
export "FLUTTER_ROOT=/Users/kima/development/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/Users/kima/Documents/refilc/app/naplo/filcnaplo"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_TARGET=/Users/kima/Documents/refilc/app/naplo/filcnaplo/lib/main.dart"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=4.1.0"
|
||||
export "FLUTTER_BUILD_NUMBER=213"
|
||||
export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC8yYTM0MDFjOWJiYjVhOWE5YWVjNzRkNGY3MzVkMThhOWRkM2ViZjJkLw=="
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=true"
|
||||
export "TREE_SHAKE_ICONS=false"
|
||||
export "PACKAGE_CONFIG=/Users/kima/Documents/refilc/app/naplo/filcnaplo/.dart_tool/package_config.json"
|
@ -1,237 +0,0 @@
|
||||
PODS:
|
||||
- app_group_directory (1.0.0):
|
||||
- Flutter
|
||||
- background_fetch (1.1.6):
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- DKImagePickerController/Core (4.3.4):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.4)
|
||||
- DKImagePickerController/PhotoGallery (4.3.4):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.4)
|
||||
- DKPhotoGallery (0.0.17):
|
||||
- DKPhotoGallery/Core (= 0.0.17)
|
||||
- DKPhotoGallery/Model (= 0.0.17)
|
||||
- DKPhotoGallery/Preview (= 0.0.17)
|
||||
- DKPhotoGallery/Resource (= 0.0.17)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_custom_tabs (0.0.1):
|
||||
- Flutter
|
||||
- flutter_image_compress (1.0.0):
|
||||
- Flutter
|
||||
- Mantle
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_image (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (0.0.1):
|
||||
- Flutter
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- home_widget (0.0.1):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
- libwebp/webp (= 1.2.4)
|
||||
- libwebp/demux (1.2.4):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.2.4):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.4)
|
||||
- live_activities (0.0.1):
|
||||
- Flutter
|
||||
- Mantle (2.2.0):
|
||||
- Mantle/extobjc (= 2.2.0)
|
||||
- Mantle/extobjc (2.2.0)
|
||||
- open_file (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.0.4):
|
||||
- Flutter
|
||||
- quick_actions_ios (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- SDWebImage (5.13.2):
|
||||
- SDWebImage/Core (= 5.13.2)
|
||||
- SDWebImage/Core (5.13.2)
|
||||
- SDWebImageWebPCoder (0.9.1):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.13)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- SwiftyGif (5.4.3)
|
||||
- uni_links (0.0.1):
|
||||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- workmanager (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_group_directory (from `.symlinks/plugins/app_group_directory/ios`)
|
||||
- background_fetch (from `.symlinks/plugins/background_fetch/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- DKImagePickerController/PhotoGallery (from `https://github.com/zhangao0086/DKImagePickerController.git`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_custom_tabs (from `.symlinks/plugins/flutter_custom_tabs/ios`)
|
||||
- flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_image (from `.symlinks/plugins/flutter_native_image/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- live_activities (from `.symlinks/plugins/live_activities/ios`)
|
||||
- open_file (from `.symlinks/plugins/open_file/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- quick_actions_ios (from `.symlinks/plugins/quick_actions_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- workmanager (from `.symlinks/plugins/workmanager/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKPhotoGallery
|
||||
- FMDB
|
||||
- libwebp
|
||||
- Mantle
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- SwiftyGif
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_group_directory:
|
||||
:path: ".symlinks/plugins/app_group_directory/ios"
|
||||
background_fetch:
|
||||
:path: ".symlinks/plugins/background_fetch/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
DKImagePickerController:
|
||||
:git: https://github.com/zhangao0086/DKImagePickerController.git
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_custom_tabs:
|
||||
:path: ".symlinks/plugins/flutter_custom_tabs/ios"
|
||||
flutter_image_compress:
|
||||
:path: ".symlinks/plugins/flutter_image_compress/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_native_image:
|
||||
:path: ".symlinks/plugins/flutter_native_image/ios"
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
home_widget:
|
||||
:path: ".symlinks/plugins/home_widget/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
live_activities:
|
||||
:path: ".symlinks/plugins/live_activities/ios"
|
||||
open_file:
|
||||
:path: ".symlinks/plugins/open_file/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
quick_actions_ios:
|
||||
:path: ".symlinks/plugins/quick_actions_ios/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
uni_links:
|
||||
:path: ".symlinks/plugins/uni_links/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
workmanager:
|
||||
:path: ".symlinks/plugins/workmanager/ios"
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
DKImagePickerController:
|
||||
:commit: a727e44718a67e300089174e5591166045815ba4
|
||||
:git: https://github.com/zhangao0086/DKImagePickerController.git
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_group_directory: 7bf9f8f9819ead554de29da7c25fb7a680d6a9a0
|
||||
background_fetch: bc9b44b0bf8b434e282a2ac9be8662800a0296ed
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_custom_tabs: 7a10a08686955cb748e5d26e0ae586d30689bf89
|
||||
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
flutter_native_image: 9c0b7451838484458e5b0fae007b86a4c2d4bdfe
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
home_widget: 2829415127ee92e876f816cbbe44c0b6601b8a37
|
||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
live_activities: 9ff56a06a2d43ecd68f56deeed13b18a8304789c
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
|
||||
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
|
||||
quick_actions_ios: 9e80dcfadfbc5d47d9cf8f47bcf428b11cf383d4
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
|
||||
SDWebImageWebPCoder: 18503de6621dd2c420d680e33d46bf8e1d5169b0
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||
|
||||
PODFILE CHECKSUM: efe5e2e257c61a1c3d5be693ef1a8ea1c4b81d8f
|
||||
|
||||
COCOAPODS: 1.12.1
|
@ -198,9 +198,9 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3127F79F28EAEDE300C2EFB3 /* Build configuration list for PBXNativeTarget "livecard" */;
|
||||
buildPhases = (
|
||||
3127F78A28EAEDE200C2EFB3 /* Resources */,
|
||||
3127F78828EAEDE200C2EFB3 /* Sources */,
|
||||
3127F78928EAEDE200C2EFB3 /* Frameworks */,
|
||||
3127F78A28EAEDE200C2EFB3 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -299,7 +299,7 @@
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 12;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
@ -310,11 +310,11 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
||||
};
|
||||
71459C0EB905E05018E3D78F /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 12;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
@ -364,7 +364,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@ -488,7 +488,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilc.naplo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilctest.naplo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -526,7 +526,7 @@
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilc.naplo.livecardpro;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilctest.naplo.livecardpro;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -567,7 +567,7 @@
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilc.naplo.livecardpro;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilctest.naplo.livecardpro;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -607,7 +607,7 @@
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilc.naplo.livecardpro;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilctest.naplo.livecardpro;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -746,7 +746,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilc.naplo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilctest.naplo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@ -774,7 +774,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.6.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilc.naplo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.refilctest.naplo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.refilcnaplo.livecard</string>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
class LessonData {
|
||||
var color: String
|
||||
var icon: String
|
||||
var index: String
|
||||
var title: String
|
||||
@ -15,6 +16,7 @@ class LessonData {
|
||||
init?() {
|
||||
let sharedDefault = UserDefaults(suiteName: "group.refilc.livecard")!
|
||||
|
||||
self.color = sharedDefault.string(forKey: "color")!
|
||||
self.icon = sharedDefault.string(forKey: "icon")!
|
||||
self.index = sharedDefault.string(forKey: "index")!
|
||||
self.title = sharedDefault.string(forKey: "title")!
|
||||
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.refilcnaplo.livecard</string>
|
||||
|
@ -68,6 +68,9 @@ struct LockScreenLiveActivityView: View {
|
||||
.monospacedDigit()
|
||||
.padding(.trailing, CGFloat(24))
|
||||
}
|
||||
.activityBackgroundTint(
|
||||
lesson!.color != "0xFF676767" ? Color(lesson!.color) : nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/models/supporter.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
|
||||
@ -68,7 +69,9 @@ class FilcAPI {
|
||||
http.Response res = await http.get(Uri.parse(config), headers: headers);
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
if (kDebugMode) {
|
||||
print(jsonDecode(res.body));
|
||||
}
|
||||
return Config.fromJson(jsonDecode(res.body));
|
||||
} else if (res.statusCode == 429) {
|
||||
res = await http.get(Uri.parse(config));
|
||||
|
@ -57,14 +57,14 @@ Future loginApi({
|
||||
String nonceStr = await Provider.of<KretaClient>(context, listen: false)
|
||||
.getAPI(KretaAPI.nonce, json: false);
|
||||
|
||||
Nonce nonce = getNonce(nonceStr, username.replaceAll(' ', '') + ' ', instituteCode);
|
||||
Nonce nonce = getNonce(nonceStr, username, instituteCode);
|
||||
headers.addAll(nonce.header());
|
||||
|
||||
Map? res = await Provider.of<KretaClient>(context, listen: false)
|
||||
.postAPI(KretaAPI.login,
|
||||
headers: headers,
|
||||
body: User.loginBody(
|
||||
username: username.replaceAll(' ', '') + ' ',
|
||||
username: username,
|
||||
password: password,
|
||||
instituteCode: instituteCode,
|
||||
));
|
||||
@ -83,7 +83,7 @@ Future loginApi({
|
||||
.getAPI(KretaAPI.student(instituteCode));
|
||||
Student student = Student.fromJson(studentJson!);
|
||||
var user = User(
|
||||
username: username.replaceAll(' ', '') + ' ',
|
||||
username: username,
|
||||
password: password,
|
||||
instituteCode: instituteCode,
|
||||
name: student.name,
|
||||
|
@ -2,7 +2,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo/database/query.dart';
|
||||
import 'package:filcnaplo/database/store.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
|
@ -19,7 +19,8 @@ enum LiveCardState {
|
||||
duringBreak,
|
||||
morning,
|
||||
afternoon,
|
||||
night
|
||||
night,
|
||||
summary
|
||||
}
|
||||
|
||||
class LiveCardProvider extends ChangeNotifier {
|
||||
@ -39,6 +40,8 @@ class LiveCardProvider extends ChangeNotifier {
|
||||
String? _latestActivityId;
|
||||
Map<String, String> _lastActivity = {};
|
||||
|
||||
bool _hasCheckedTimetable = false;
|
||||
|
||||
LiveCardProvider({
|
||||
required TimetableProvider timetable,
|
||||
required SettingsProvider settings,
|
||||
@ -47,7 +50,9 @@ class LiveCardProvider extends ChangeNotifier {
|
||||
// Check if live card is enabled .areActivitiesEnabled()
|
||||
_liveActivitiesPlugin.areActivitiesEnabled().then((value) {
|
||||
// Console log
|
||||
if (kDebugMode) {
|
||||
print("Live card enabled: $value");
|
||||
}
|
||||
|
||||
if (value) {
|
||||
_liveActivitiesPlugin.init(appGroupId: "group.refilc.livecard");
|
||||
@ -105,6 +110,7 @@ class LiveCardProvider extends ChangeNotifier {
|
||||
switch (currentState) {
|
||||
case LiveCardState.duringLesson:
|
||||
return {
|
||||
"color": _settings.liveActivityColor.toString(),
|
||||
"icon": currentLesson != null
|
||||
? SubjectIcon.resolveName(subject: currentLesson?.subject)
|
||||
: "book",
|
||||
@ -137,6 +143,7 @@ class LiveCardProvider extends ChangeNotifier {
|
||||
final diff = getFloorDifference();
|
||||
|
||||
return {
|
||||
"color": _settings.liveActivityColor.toString(),
|
||||
"icon": iconFloorMap[diff] ?? "cup.and.saucer",
|
||||
"title": "Szünet",
|
||||
"description": "go $diff".i18n.fill([
|
||||
@ -195,7 +202,8 @@ class LiveCardProvider extends ChangeNotifier {
|
||||
|
||||
List<Lesson> today = _today(_timetable);
|
||||
|
||||
if (today.isEmpty) {
|
||||
if (today.isEmpty && !_hasCheckedTimetable) {
|
||||
_hasCheckedTimetable = true;
|
||||
await _timetable.fetch(week: Week.current());
|
||||
today = _today(_timetable);
|
||||
}
|
||||
@ -249,7 +257,10 @@ class LiveCardProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLesson != null) {
|
||||
if (now.isBefore(DateTime(now.year, DateTime.august, 31)) &&
|
||||
now.isAfter(DateTime(now.year, DateTime.june, 14))) {
|
||||
currentState = LiveCardState.summary;
|
||||
} else if (currentLesson != null) {
|
||||
currentState = LiveCardState.duringLesson;
|
||||
} else if (nextLesson != null && prevLesson != null) {
|
||||
currentState = LiveCardState.duringBreak;
|
||||
|
@ -1,7 +1,4 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/api/client.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
@ -11,7 +8,7 @@ import 'package:provider/provider.dart';
|
||||
class NewsProvider extends ChangeNotifier {
|
||||
// Private
|
||||
late List<News> _news;
|
||||
late int _state;
|
||||
//late int _state;
|
||||
late int _fresh;
|
||||
bool show = false;
|
||||
late BuildContext _context;
|
||||
@ -30,56 +27,83 @@ class NewsProvider extends ChangeNotifier {
|
||||
|
||||
Future<void> restore() async {
|
||||
// Load news state from the database
|
||||
var state_ = Provider.of<SettingsProvider>(_context, listen: false).newsState;
|
||||
var seen_ = Provider.of<SettingsProvider>(_context, listen: false).seenNews;
|
||||
|
||||
if (state_ == -1) {
|
||||
if (seen_.isEmpty) {
|
||||
var news_ = await FilcAPI.getNews();
|
||||
if (news_ != null) {
|
||||
state_ = news_.length;
|
||||
_news = news_;
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
|
||||
_state = state_;
|
||||
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
|
||||
//_state = seen_;
|
||||
// Provider.of<SettingsProvider>(_context, listen: false)
|
||||
// .update(seenNewsId: news_.id);
|
||||
}
|
||||
|
||||
Future<void> fetch() async {
|
||||
var news_ = await FilcAPI.getNews();
|
||||
if (news_ == null) return;
|
||||
|
||||
show = false;
|
||||
|
||||
_news = news_;
|
||||
_fresh = news_.length - _state;
|
||||
|
||||
if (_fresh < 0) {
|
||||
_state = news_.length;
|
||||
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
|
||||
}
|
||||
|
||||
_fresh = max(_fresh, 0);
|
||||
|
||||
if (_fresh > 0) {
|
||||
for (var news in news_) {
|
||||
if (news.expireDate.isAfter(DateTime.now()) &&
|
||||
Provider.of<SettingsProvider>(_context, listen: false)
|
||||
.seenNews
|
||||
.contains(news.id) ==
|
||||
false) {
|
||||
show = true;
|
||||
Provider.of<SettingsProvider>(_context, listen: false)
|
||||
.update(seenNewsId: news.id);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
// print(news_.length);
|
||||
// print(_state);
|
||||
|
||||
// _news = news_;
|
||||
// _fresh = news_.length - _state;
|
||||
|
||||
// if (_fresh < 0) {
|
||||
// _state = news_.length;
|
||||
// Provider.of<SettingsProvider>(_context, listen: false)
|
||||
// .update(newsState: _state);
|
||||
// }
|
||||
|
||||
// _fresh = max(_fresh, 0);
|
||||
|
||||
// if (_fresh > 0) {
|
||||
// show = true;
|
||||
// notifyListeners();
|
||||
// }
|
||||
|
||||
// print(_fresh);
|
||||
// print(_state);
|
||||
// print(show);
|
||||
}
|
||||
|
||||
void lock() => show = false;
|
||||
|
||||
void release() {
|
||||
if (_fresh == 0) return;
|
||||
// if (_fresh == 0) return;
|
||||
|
||||
_fresh--;
|
||||
_state++;
|
||||
// _fresh--;
|
||||
// //_state++;
|
||||
|
||||
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
|
||||
// // Provider.of<SettingsProvider>(_context, listen: false)
|
||||
// // .update(seenNewsId: _state);
|
||||
|
||||
if (_fresh > 0) {
|
||||
show = true;
|
||||
} else {
|
||||
show = false;
|
||||
}
|
||||
// if (_fresh > 0) {
|
||||
// show = true;
|
||||
// } else {
|
||||
// show = false;
|
||||
// }
|
||||
|
||||
notifyListeners();
|
||||
// notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import 'package:filcnaplo/theme/theme.dart';
|
||||
import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
@ -113,7 +114,8 @@ class App extends StatelessWidget {
|
||||
ChangeNotifierProvider<ExamProvider>(
|
||||
create: (context) => ExamProvider(context: context)),
|
||||
ChangeNotifierProvider<HomeworkProvider>(
|
||||
create: (context) => HomeworkProvider(context: context)),
|
||||
create: (context) =>
|
||||
HomeworkProvider(context: context, database: database)),
|
||||
ChangeNotifierProvider<MessageProvider>(
|
||||
create: (context) => MessageProvider(context: context)),
|
||||
ChangeNotifierProvider<NoteProvider>(
|
||||
@ -194,8 +196,19 @@ class App extends StatelessWidget {
|
||||
}
|
||||
|
||||
Route? rootNavigator(RouteSettings route) {
|
||||
// if platform == android || platform == ios
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
if (kIsWeb) {
|
||||
switch (route.name) {
|
||||
case "login_back":
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => const desktop.LoginScreen(back: true));
|
||||
case "login":
|
||||
return _rootRoute(const desktop.LoginScreen());
|
||||
case "navigation":
|
||||
return _rootRoute(const desktop.NavigationScreen());
|
||||
case "login_to_navigation":
|
||||
return desktop.loginRoute(const desktop.NavigationScreen());
|
||||
}
|
||||
} else if (Platform.isAndroid || Platform.isIOS) {
|
||||
switch (route.name) {
|
||||
case "login_back":
|
||||
return CupertinoPageRoute(
|
||||
|
@ -5,41 +5,64 @@ import 'dart:io';
|
||||
import 'package:filcnaplo/api/providers/database_provider.dart';
|
||||
import 'package:filcnaplo/database/struct.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
||||
|
||||
const settingsDB = DatabaseStruct("settings", {
|
||||
"language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int,
|
||||
"update_channel": int, "config": String, "custom_accent_color": int, "custom_background_color": int, "custom_highlight_color": int, // general
|
||||
"grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors
|
||||
"language": String, "start_page": int, "rounding": int, "theme": int,
|
||||
"accent_color": int, "news": int, "seen_news": String,
|
||||
"developer_mode": int,
|
||||
"update_channel": int, "config": String, "custom_accent_color": int,
|
||||
"custom_background_color": int, "custom_highlight_color": int, // general
|
||||
"grade_color1": int, "grade_color2": int, "grade_color3": int,
|
||||
"grade_color4": int, "grade_color5": int, // grade colors
|
||||
"vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int,
|
||||
"notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications
|
||||
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "bell_delay": int, "bell_delay_enabled": int,
|
||||
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "premium_token": String, "premium_login": String,
|
||||
"last_account_id": String, "renamed_subjects_enabled": int, "renamed_subjects_italics":int,
|
||||
"notifications": int, "notifications_bitfield": int,
|
||||
"notification_poll_interval": int, // notifications
|
||||
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int,
|
||||
"bell_delay": int, "bell_delay_enabled": int,
|
||||
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String,
|
||||
"premium_token": String, "premium_login": String,
|
||||
"last_account_id": String, "renamed_subjects_enabled": int,
|
||||
"renamed_subjects_italics": int, "renamed_teachers_enabled": int,
|
||||
"renamed_teachers_italics": int,
|
||||
"live_activity_color": String,
|
||||
});
|
||||
// DON'T FORGET TO UPDATE DEFAULT VALUES IN `initDB` MIGRATION OR ELSE PARENTS WILL COMPLAIN ABOUT THEIR CHILDREN MISSING
|
||||
// YOU'VE BEEN WARNED!!!
|
||||
const usersDB = DatabaseStruct("users", {
|
||||
"id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int,
|
||||
"id": String, "name": String, "username": String, "password": String,
|
||||
"institute_code": String, "student": String, "role": int,
|
||||
"nickname": String, "picture": String // premium only
|
||||
});
|
||||
const userDataDB = DatabaseStruct("user_data", {
|
||||
"id": String, "grades": String, "timetable": String, "exams": String, "homework": String, "messages": String, "notes": String,
|
||||
"id": String, "grades": String, "timetable": String, "exams": String,
|
||||
"homework": String, "messages": String, "notes": String,
|
||||
"events": String, "absences": String, "group_averages": String,
|
||||
// renamed subjects // non kreta data
|
||||
"renamed_subjects": String,
|
||||
// renamed teachers // non kreta data
|
||||
"renamed_teachers": String,
|
||||
// "subject_lesson_count": String, // non kreta data
|
||||
"last_seen_grade": int,
|
||||
// goal planning // non kreta data
|
||||
"goal_plans": String,
|
||||
"goal_averages": String,
|
||||
"goal_befores": String,
|
||||
"goal_pin_dates": String,
|
||||
});
|
||||
|
||||
Future<void> createTable(Database db, DatabaseStruct struct) => db.execute("CREATE TABLE IF NOT EXISTS ${struct.table} ($struct)");
|
||||
Future<void> createTable(Database db, DatabaseStruct struct) =>
|
||||
db.execute("CREATE TABLE IF NOT EXISTS ${struct.table} ($struct)");
|
||||
|
||||
Future<Database> initDB(DatabaseProvider database) async {
|
||||
Database db;
|
||||
|
||||
if (Platform.isLinux || Platform.isWindows) {
|
||||
if (kIsWeb) {
|
||||
db = await databaseFactoryFfiWeb.openDatabase("app.db");
|
||||
} else if (Platform.isLinux || Platform.isWindows) {
|
||||
sqfliteFfiInit();
|
||||
db = await databaseFactoryFfi.openDatabase("app.db");
|
||||
} else {
|
||||
@ -50,9 +73,11 @@ Future<Database> initDB(DatabaseProvider database) async {
|
||||
await createTable(db, usersDB);
|
||||
await createTable(db, userDataDB);
|
||||
|
||||
if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first == 0) {
|
||||
if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first ==
|
||||
0) {
|
||||
// Set default values for table Settings
|
||||
await db.insert("settings", SettingsProvider.defaultSettings(database: database).toMap());
|
||||
await db.insert("settings",
|
||||
SettingsProvider.defaultSettings(database: database).toMap());
|
||||
}
|
||||
|
||||
// Migrate Databases
|
||||
@ -60,7 +85,8 @@ Future<Database> initDB(DatabaseProvider database) async {
|
||||
await migrateDB(
|
||||
db,
|
||||
struct: settingsDB,
|
||||
defaultValues: SettingsProvider.defaultSettings(database: database).toMap(),
|
||||
defaultValues:
|
||||
SettingsProvider.defaultSettings(database: database).toMap(),
|
||||
);
|
||||
await migrateDB(
|
||||
db,
|
||||
@ -68,12 +94,20 @@ Future<Database> initDB(DatabaseProvider database) async {
|
||||
defaultValues: {"role": 0, "nickname": "", "picture": ""},
|
||||
);
|
||||
await migrateDB(db, struct: userDataDB, defaultValues: {
|
||||
"grades": "[]", "timetable": "[]", "exams": "[]", "homework": "[]", "messages": "[]", "notes": "[]", "events": "[]", "absences": "[]",
|
||||
"grades": "[]", "timetable": "[]", "exams": "[]", "homework": "[]",
|
||||
"messages": "[]", "notes": "[]", "events": "[]", "absences": "[]",
|
||||
"group_averages": "[]",
|
||||
// renamed subjects // non kreta data
|
||||
"renamed_subjects": "{}",
|
||||
// renamed teachers // non kreta data
|
||||
"renamed_teachers": "{}",
|
||||
// "subject_lesson_count": "{}", // non kreta data
|
||||
"last_seen_grade": 0,
|
||||
// goal planning // non kreta data
|
||||
"goal_plans": "{}",
|
||||
"goal_averages": "{}",
|
||||
"goal_befores": "{}",
|
||||
"goal_pin_dates": "{}",
|
||||
});
|
||||
} catch (error) {
|
||||
print("ERROR: migrateDB: $error");
|
||||
@ -99,7 +133,8 @@ Future<void> migrateDB(
|
||||
|
||||
// go through each row and add missing keys or delete non existing keys
|
||||
await Future.forEach<Map<String, Object?>>(originalRows, (original) async {
|
||||
bool migrationRequired = struct.struct.keys.any((key) => !original.containsKey(key) || original[key] == null) ||
|
||||
bool migrationRequired = struct.struct.keys.any(
|
||||
(key) => !original.containsKey(key) || original[key] == null) ||
|
||||
original.keys.any((key) => !struct.struct.containsKey(key));
|
||||
|
||||
if (migrationRequired) {
|
||||
|
@ -26,7 +26,8 @@ class DatabaseQuery {
|
||||
|
||||
Future<SettingsProvider> getSettings(DatabaseProvider database) async {
|
||||
Map settingsMap = (await db.query("settings")).elementAt(0);
|
||||
SettingsProvider settings = SettingsProvider.fromMap(settingsMap, database: database);
|
||||
SettingsProvider settings =
|
||||
SettingsProvider.fromMap(settingsMap, database: database);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@ -36,7 +37,10 @@ class DatabaseQuery {
|
||||
for (var user in usersMap) {
|
||||
userProvider.addUser(User.fromMap(user));
|
||||
}
|
||||
if (userProvider.getUsers().map((e) => e.id).contains(settings.lastAccountId)) {
|
||||
if (userProvider
|
||||
.getUsers()
|
||||
.map((e) => e.id)
|
||||
.contains(settings.lastAccountId)) {
|
||||
userProvider.setUser(settings.lastAccountId);
|
||||
} else {
|
||||
if (usersMap.isNotEmpty) {
|
||||
@ -54,100 +58,133 @@ class UserDatabaseQuery {
|
||||
final Database db;
|
||||
|
||||
Future<List<Grade>> getGrades({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? gradesJson = userData.elementAt(0)["grades"] as String?;
|
||||
if (gradesJson == null) return [];
|
||||
List<Grade> grades = (jsonDecode(gradesJson) as List).map((e) => Grade.fromJson(e)).toList();
|
||||
List<Grade> grades =
|
||||
(jsonDecode(gradesJson) as List).map((e) => Grade.fromJson(e)).toList();
|
||||
return grades;
|
||||
}
|
||||
|
||||
Future<Map<Week, List<Lesson>>> getLessons({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? lessonsJson = userData.elementAt(0)["timetable"] as String?;
|
||||
if (lessonsJson == null) return {};
|
||||
if (jsonDecode(lessonsJson) is List) return {};
|
||||
Map<Week, List<Lesson>> lessons = (jsonDecode(lessonsJson) as Map).cast<String, List>().map((key, value) {
|
||||
return MapEntry(Week.fromId(int.parse(key)), value.cast<Map<String, Object?>>().map((e) => Lesson.fromJson(e)).toList());
|
||||
Map<Week, List<Lesson>> lessons =
|
||||
(jsonDecode(lessonsJson) as Map).cast<String, List>().map((key, value) {
|
||||
return MapEntry(
|
||||
Week.fromId(int.parse(key)),
|
||||
value
|
||||
.cast<Map<String, Object?>>()
|
||||
.map((e) => Lesson.fromJson(e))
|
||||
.toList());
|
||||
}).cast();
|
||||
return lessons;
|
||||
}
|
||||
|
||||
Future<List<Exam>> getExams({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? examsJson = userData.elementAt(0)["exams"] as String?;
|
||||
if (examsJson == null) return [];
|
||||
List<Exam> exams = (jsonDecode(examsJson) as List).map((e) => Exam.fromJson(e)).toList();
|
||||
List<Exam> exams =
|
||||
(jsonDecode(examsJson) as List).map((e) => Exam.fromJson(e)).toList();
|
||||
return exams;
|
||||
}
|
||||
|
||||
Future<List<Homework>> getHomework({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? homeworkJson = userData.elementAt(0)["homework"] as String?;
|
||||
if (homeworkJson == null) return [];
|
||||
List<Homework> homework = (jsonDecode(homeworkJson) as List).map((e) => Homework.fromJson(e)).toList();
|
||||
List<Homework> homework = (jsonDecode(homeworkJson) as List)
|
||||
.map((e) => Homework.fromJson(e))
|
||||
.toList();
|
||||
return homework;
|
||||
}
|
||||
|
||||
Future<List<Message>> getMessages({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? messagesJson = userData.elementAt(0)["messages"] as String?;
|
||||
if (messagesJson == null) return [];
|
||||
List<Message> messages = (jsonDecode(messagesJson) as List).map((e) => Message.fromJson(e)).toList();
|
||||
List<Message> messages = (jsonDecode(messagesJson) as List)
|
||||
.map((e) => Message.fromJson(e))
|
||||
.toList();
|
||||
return messages;
|
||||
}
|
||||
|
||||
Future<List<Note>> getNotes({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? notesJson = userData.elementAt(0)["notes"] as String?;
|
||||
if (notesJson == null) return [];
|
||||
List<Note> notes = (jsonDecode(notesJson) as List).map((e) => Note.fromJson(e)).toList();
|
||||
List<Note> notes =
|
||||
(jsonDecode(notesJson) as List).map((e) => Note.fromJson(e)).toList();
|
||||
return notes;
|
||||
}
|
||||
|
||||
Future<List<Event>> getEvents({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? eventsJson = userData.elementAt(0)["events"] as String?;
|
||||
if (eventsJson == null) return [];
|
||||
List<Event> events = (jsonDecode(eventsJson) as List).map((e) => Event.fromJson(e)).toList();
|
||||
List<Event> events =
|
||||
(jsonDecode(eventsJson) as List).map((e) => Event.fromJson(e)).toList();
|
||||
return events;
|
||||
}
|
||||
|
||||
Future<List<Absence>> getAbsences({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? absencesJson = userData.elementAt(0)["absences"] as String?;
|
||||
if (absencesJson == null) return [];
|
||||
List<Absence> absences = (jsonDecode(absencesJson) as List).map((e) => Absence.fromJson(e)).toList();
|
||||
List<Absence> absences = (jsonDecode(absencesJson) as List)
|
||||
.map((e) => Absence.fromJson(e))
|
||||
.toList();
|
||||
return absences;
|
||||
}
|
||||
|
||||
Future<List<GroupAverage>> getGroupAverages({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return [];
|
||||
String? groupAveragesJson = userData.elementAt(0)["group_averages"] as String?;
|
||||
String? groupAveragesJson =
|
||||
userData.elementAt(0)["group_averages"] as String?;
|
||||
if (groupAveragesJson == null) return [];
|
||||
List<GroupAverage> groupAverages = (jsonDecode(groupAveragesJson) as List).map((e) => GroupAverage.fromJson(e)).toList();
|
||||
List<GroupAverage> groupAverages = (jsonDecode(groupAveragesJson) as List)
|
||||
.map((e) => GroupAverage.fromJson(e))
|
||||
.toList();
|
||||
return groupAverages;
|
||||
}
|
||||
|
||||
Future<SubjectLessonCount> getSubjectLessonCount({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
Future<SubjectLessonCount> getSubjectLessonCount(
|
||||
{required String userId}) async {
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return SubjectLessonCount.fromMap({});
|
||||
String? lessonCountJson = userData.elementAt(0)["subject_lesson_count"] as String?;
|
||||
String? lessonCountJson =
|
||||
userData.elementAt(0)["subject_lesson_count"] as String?;
|
||||
if (lessonCountJson == null) return SubjectLessonCount.fromMap({});
|
||||
SubjectLessonCount lessonCount = SubjectLessonCount.fromMap(jsonDecode(lessonCountJson) as Map);
|
||||
SubjectLessonCount lessonCount =
|
||||
SubjectLessonCount.fromMap(jsonDecode(lessonCountJson) as Map);
|
||||
return lessonCount;
|
||||
}
|
||||
|
||||
Future<DateTime> lastSeenGrade({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return DateTime(0);
|
||||
int? lastSeenDate = userData.elementAt(0)["last_seen_grade"] as int?;
|
||||
if (lastSeenDate == null) return DateTime(0);
|
||||
@ -155,11 +192,71 @@ class UserDatabaseQuery {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
// renamed things
|
||||
Future<Map<String, String>> renamedSubjects({required String userId}) async {
|
||||
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? renamedSubjectsJson = userData.elementAt(0)["renamed_subjects"] as String?;
|
||||
String? renamedSubjectsJson =
|
||||
userData.elementAt(0)["renamed_subjects"] as String?;
|
||||
if (renamedSubjectsJson == null) return {};
|
||||
return (jsonDecode(renamedSubjectsJson) as Map).map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
return (jsonDecode(renamedSubjectsJson) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
|
||||
Future<Map<String, String>> renamedTeachers({required String userId}) async {
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? renamedTeachersJson =
|
||||
userData.elementAt(0)["renamed_teachers"] as String?;
|
||||
if (renamedTeachersJson == null) return {};
|
||||
return (jsonDecode(renamedTeachersJson) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
|
||||
// goal planner
|
||||
Future<Map<String, String>> subjectGoalPlans({required String userId}) async {
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? goalPlansJson = userData.elementAt(0)["goal_plans"] as String?;
|
||||
if (goalPlansJson == null) return {};
|
||||
return (jsonDecode(goalPlansJson) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
|
||||
Future<Map<String, String>> subjectGoalAverages(
|
||||
{required String userId}) async {
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? goalAvgsJson = userData.elementAt(0)["goal_averages"] as String?;
|
||||
if (goalAvgsJson == null) return {};
|
||||
return (jsonDecode(goalAvgsJson) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
|
||||
Future<Map<String, String>> subjectGoalBefores(
|
||||
{required String userId}) async {
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? goalBeforesJson = userData.elementAt(0)["goal_befores"] as String?;
|
||||
if (goalBeforesJson == null) return {};
|
||||
return (jsonDecode(goalBeforesJson) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
|
||||
Future<Map<String, String>> subjectGoalPinDates(
|
||||
{required String userId}) async {
|
||||
List<Map> userData =
|
||||
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
|
||||
if (userData.isEmpty) return {};
|
||||
String? goalPinDatesJson =
|
||||
userData.elementAt(0)["goal_pin_dates"] as String?;
|
||||
if (goalPinDatesJson == null) return {};
|
||||
return (jsonDecode(goalPinDatesJson) as Map)
|
||||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,11 @@ class DatabaseStore {
|
||||
}
|
||||
|
||||
Future<void> storeUser(User user) async {
|
||||
List userRes = await db.query("users", where: "id = ?", whereArgs: [user.id]);
|
||||
List userRes =
|
||||
await db.query("users", where: "id = ?", whereArgs: [user.id]);
|
||||
if (userRes.isNotEmpty) {
|
||||
await db.update("users", user.toMap(), where: "id = ?", whereArgs: [user.id]);
|
||||
await db
|
||||
.update("users", user.toMap(), where: "id = ?", whereArgs: [user.id]);
|
||||
} else {
|
||||
await db.insert("users", user.toMap());
|
||||
await db.insert("user_data", {"id": user.id});
|
||||
@ -49,64 +51,123 @@ class UserDatabaseStore {
|
||||
|
||||
Future<void> storeGrades(List<Grade> grades, {required String userId}) async {
|
||||
String gradesJson = jsonEncode(grades.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"grades": gradesJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"grades": gradesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeLessons(Map<Week, List<Lesson>?> lessons, {required String userId}) async {
|
||||
Future<void> storeLessons(Map<Week, List<Lesson>?> lessons,
|
||||
{required String userId}) async {
|
||||
final map = lessons.map<String, List<Map<String, Object?>>>(
|
||||
(k, v) => MapEntry(k.id.toString(), v!.where((e) => e.json != null).map((e) => e.json!).toList().cast()),
|
||||
(k, v) => MapEntry(k.id.toString(),
|
||||
v!.where((e) => e.json != null).map((e) => e.json!).toList().cast()),
|
||||
);
|
||||
String lessonsJson = jsonEncode(map);
|
||||
await db.update("user_data", {"timetable": lessonsJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"timetable": lessonsJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeExams(List<Exam> exams, {required String userId}) async {
|
||||
String examsJson = jsonEncode(exams.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"exams": examsJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"exams": examsJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeHomework(List<Homework> homework, {required String userId}) async {
|
||||
Future<void> storeHomework(List<Homework> homework,
|
||||
{required String userId}) async {
|
||||
String homeworkJson = jsonEncode(homework.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"homework": homeworkJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"homework": homeworkJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeMessages(List<Message> messages, {required String userId}) async {
|
||||
Future<void> storeMessages(List<Message> messages,
|
||||
{required String userId}) async {
|
||||
String messagesJson = jsonEncode(messages.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"messages": messagesJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"messages": messagesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeNotes(List<Note> notes, {required String userId}) async {
|
||||
String notesJson = jsonEncode(notes.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"notes": notesJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"notes": notesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeEvents(List<Event> events, {required String userId}) async {
|
||||
String eventsJson = jsonEncode(events.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"events": eventsJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"events": eventsJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeAbsences(List<Absence> absences, {required String userId}) async {
|
||||
Future<void> storeAbsences(List<Absence> absences,
|
||||
{required String userId}) async {
|
||||
String absencesJson = jsonEncode(absences.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"absences": absencesJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"absences": absencesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeGroupAverages(List<GroupAverage> groupAverages, {required String userId}) async {
|
||||
String groupAveragesJson = jsonEncode(groupAverages.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"group_averages": groupAveragesJson}, where: "id = ?", whereArgs: [userId]);
|
||||
Future<void> storeGroupAverages(List<GroupAverage> groupAverages,
|
||||
{required String userId}) async {
|
||||
String groupAveragesJson =
|
||||
jsonEncode(groupAverages.map((e) => e.json).toList());
|
||||
await db.update("user_data", {"group_averages": groupAveragesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeSubjectLessonCount(SubjectLessonCount lessonCount, {required String userId}) async {
|
||||
Future<void> storeSubjectLessonCount(SubjectLessonCount lessonCount,
|
||||
{required String userId}) async {
|
||||
String lessonCountJson = jsonEncode(lessonCount.toMap());
|
||||
await db.update("user_data", {"subject_lesson_count": lessonCountJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"subject_lesson_count": lessonCountJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeLastSeenGrade(DateTime date, {required String userId}) async {
|
||||
Future<void> storeLastSeenGrade(DateTime date,
|
||||
{required String userId}) async {
|
||||
int lastSeenDate = date.millisecondsSinceEpoch;
|
||||
await db.update("user_data", {"last_seen_grade": lastSeenDate}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"last_seen_grade": lastSeenDate},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeRenamedSubjects(Map<String, String> subjects, {required String userId}) async {
|
||||
// renamed things
|
||||
Future<void> storeRenamedSubjects(Map<String, String> subjects,
|
||||
{required String userId}) async {
|
||||
String renamedSubjectsJson = jsonEncode(subjects);
|
||||
await db.update("user_data", {"renamed_subjects": renamedSubjectsJson}, where: "id = ?", whereArgs: [userId]);
|
||||
await db.update("user_data", {"renamed_subjects": renamedSubjectsJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeRenamedTeachers(Map<String, String> teachers,
|
||||
{required String userId}) async {
|
||||
String renamedTeachersJson = jsonEncode(teachers);
|
||||
await db.update("user_data", {"renamed_teachers": renamedTeachersJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
// goal planner
|
||||
Future<void> storeSubjectGoalPlans(Map<String, String> plans,
|
||||
{required String userId}) async {
|
||||
String goalPlansJson = jsonEncode(plans);
|
||||
await db.update("user_data", {"goal_plans": goalPlansJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeSubjectGoalAverages(Map<String, String> avgs,
|
||||
{required String userId}) async {
|
||||
String goalAvgsJson = jsonEncode(avgs);
|
||||
await db.update("user_data", {"goal_averages": goalAvgsJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeSubjectGoalBefores(Map<String, String> befores,
|
||||
{required String userId}) async {
|
||||
String goalBeforesJson = jsonEncode(befores);
|
||||
await db.update("user_data", {"goal_befores": goalBeforesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
|
||||
Future<void> storeSubjectGoalPinDates(Map<String, String> dates,
|
||||
{required String userId}) async {
|
||||
String goalPinDatesJson = jsonEncode(dates);
|
||||
await db.update("user_data", {"goal_pin_dates": goalPinDatesJson},
|
||||
where: "id = ?", whereArgs: [userId]);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ class NotificationsHelper {
|
||||
@pragma('vm:entry-point')
|
||||
void backgroundJob() async {
|
||||
// initialize providers
|
||||
database = DatabaseProvider();
|
||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
DatabaseProvider database = DatabaseProvider();
|
||||
await database.init();
|
||||
settingsProvider =
|
||||
await database.query.getSettings(database);
|
||||
@ -54,6 +56,8 @@ class NotificationsHelper {
|
||||
|
||||
// loop through grades and see which hasn't been seen yet
|
||||
for (Grade grade in grades) {
|
||||
// if grade is not a normal grade (1-5), don't show it
|
||||
if ([1, 2, 3, 4, 5].contains(grade.value.value)) {
|
||||
// if the grade was added over a week ago, don't show it to avoid notification spam
|
||||
if (grade.seenDate.isAfter(lastSeenGrade) && grade.date.difference(DateTime.now()).inDays * -1 < 7) {
|
||||
// send notificiation about new grade
|
||||
@ -93,6 +97,7 @@ class NotificationsHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// set grade seen status
|
||||
gradeProvider.seenAll();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'package:filcnaplo/api/client.dart';
|
||||
import 'package:filcnaplo/helpers/storage_helper.dart';
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
enum UpdateState { none, preparing, downloading, installing }
|
||||
|
||||
@ -32,6 +33,10 @@ extension UpdateHelper on Release {
|
||||
|
||||
updateCallback(-1, UpdateState.installing);
|
||||
|
||||
var permStatus =
|
||||
(await Permission.manageExternalStorage.request().isGranted &&
|
||||
await Permission.requestInstallPackages.request().isGranted);
|
||||
if (permStatus) {
|
||||
var result = await OpenFile.open(apk.path);
|
||||
|
||||
if (result.type != ResultType.done) {
|
||||
@ -39,6 +44,7 @@ extension UpdateHelper on Release {
|
||||
print("ERROR: installUpdate.openFile: ${result.message}");
|
||||
throw result.message;
|
||||
}
|
||||
}
|
||||
|
||||
updateCallback(-1, UpdateState.none);
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/database_provider.dart';
|
||||
@ -30,7 +32,8 @@ void main() async {
|
||||
runApp(App(
|
||||
database: startup.database,
|
||||
settings: startup.settings,
|
||||
user: startup.user));
|
||||
user: startup.user,
|
||||
));
|
||||
}
|
||||
|
||||
class Startup {
|
||||
@ -46,16 +49,22 @@ class Startup {
|
||||
settings = await database.query.getSettings(database);
|
||||
user = await database.query.getUsers(settings);
|
||||
|
||||
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
||||
// Notifications setup
|
||||
if (!kIsWeb) {
|
||||
initPlatformState();
|
||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
}
|
||||
|
||||
// Get permission to show notifications
|
||||
flutterLocalNotificationsPlugin
|
||||
if (kIsWeb) {
|
||||
// do nothing
|
||||
} else if (Platform.isAndroid) {
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()!
|
||||
.requestPermission();
|
||||
} else if (Platform.isIOS) {
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>()
|
||||
@ -64,6 +73,7 @@ class Startup {
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
} else if (Platform.isMacOS) {
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
MacOSFlutterLocalNotificationsPlugin>()
|
||||
@ -72,9 +82,13 @@ class Startup {
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
} else if (Platform.isLinux) {
|
||||
// no permissions are needed on linux
|
||||
}
|
||||
|
||||
// Platform specific settings
|
||||
final DarwinInitializationSettings initializationSettingsDarwin =
|
||||
if (!kIsWeb) {
|
||||
const DarwinInitializationSettings initializationSettingsDarwin =
|
||||
DarwinInitializationSettings(
|
||||
requestSoundPermission: true,
|
||||
requestBadgePermission: true,
|
||||
@ -82,10 +96,14 @@ class Startup {
|
||||
);
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings('ic_notification');
|
||||
final InitializationSettings initializationSettings = InitializationSettings(
|
||||
const LinuxInitializationSettings initializationSettingsLinux =
|
||||
LinuxInitializationSettings(defaultActionName: 'Open notification');
|
||||
const InitializationSettings initializationSettings =
|
||||
InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsDarwin,
|
||||
macOS: initializationSettingsDarwin
|
||||
macOS: initializationSettingsDarwin,
|
||||
linux: initializationSettingsLinux,
|
||||
);
|
||||
|
||||
// Initialize notifications
|
||||
@ -94,6 +112,7 @@ class Startup {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool errorShown = false;
|
||||
String lastException = '';
|
||||
@ -120,9 +139,11 @@ Widget errorBuilder(FlutterErrorDetails details) {
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> initPlatformState() async {
|
||||
// Configure BackgroundFetch.
|
||||
int status = await BackgroundFetch.configure(BackgroundFetchConfig(
|
||||
int status = await BackgroundFetch.configure(
|
||||
BackgroundFetchConfig(
|
||||
minimumFetchInterval: 15,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true,
|
||||
@ -131,24 +152,30 @@ Widget errorBuilder(FlutterErrorDetails details) {
|
||||
requiresStorageNotLow: false,
|
||||
requiresDeviceIdle: false,
|
||||
requiredNetworkType: NetworkType.ANY,
|
||||
startOnBoot: true
|
||||
), (String taskId) async { // <-- Event handler
|
||||
startOnBoot: true), (String taskId) async {
|
||||
// <-- Event handler
|
||||
if (kDebugMode) {
|
||||
print("[BackgroundFetch] Event received $taskId");
|
||||
}
|
||||
NotificationsHelper().backgroundJob();
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (String taskId) async { // <-- Task timeout handler.
|
||||
}, (String taskId) async {
|
||||
// <-- Task timeout handler.
|
||||
if (kDebugMode) {
|
||||
print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
|
||||
}
|
||||
BackgroundFetch.finish(taskId);
|
||||
});
|
||||
if (kDebugMode) {
|
||||
print('[BackgroundFetch] configure success: $status');
|
||||
}
|
||||
BackgroundFetch.scheduleTask(TaskConfig(
|
||||
taskId: "com.transistorsoft.refilcnotification",
|
||||
delay: 900000, // 15 minutes
|
||||
periodic: true,
|
||||
forceAlarmManager: true,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true
|
||||
));
|
||||
enableHeadless: true));
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
@ -156,11 +183,15 @@ void backgroundHeadlessTask(HeadlessTask task) {
|
||||
String taskId = task.taskId;
|
||||
bool isTimeout = task.timeout;
|
||||
if (isTimeout) {
|
||||
if (kDebugMode) {
|
||||
print("[BackgroundFetch] Headless task timed-out: $taskId");
|
||||
}
|
||||
BackgroundFetch.finish(taskId);
|
||||
return;
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('[BackgroundFetch] Headless event received.');
|
||||
}
|
||||
NotificationsHelper().backgroundJob();
|
||||
BackgroundFetch.finish(task.taskId);
|
||||
}
|
||||
|
@ -1,30 +1,36 @@
|
||||
class News {
|
||||
String id;
|
||||
String title;
|
||||
String content;
|
||||
String link;
|
||||
String openLabel;
|
||||
String platform;
|
||||
bool emergency;
|
||||
DateTime expireDate;
|
||||
Map? json;
|
||||
|
||||
News({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.content,
|
||||
required this.link,
|
||||
required this.openLabel,
|
||||
required this.platform,
|
||||
required this.emergency,
|
||||
required this.expireDate,
|
||||
this.json,
|
||||
});
|
||||
|
||||
factory News.fromJson(Map json) {
|
||||
return News(
|
||||
id: json["id"] ?? "",
|
||||
title: json["title"] ?? "",
|
||||
content: json["content"] ?? "",
|
||||
link: json["link"] ?? "",
|
||||
openLabel: json["open_label"] ?? "",
|
||||
platform: json["platform"] ?? "",
|
||||
emergency: json["emergency"] ?? false,
|
||||
expireDate: DateTime.parse(json["expire_date"] ?? ''),
|
||||
json: json,
|
||||
);
|
||||
}
|
||||
|
21
filcnaplo/lib/models/personality.dart
Normal file
@ -0,0 +1,21 @@
|
||||
class Personality {
|
||||
PersonalityType type;
|
||||
|
||||
Personality({
|
||||
this.type = PersonalityType.npc,
|
||||
});
|
||||
}
|
||||
|
||||
enum PersonalityType {
|
||||
geek,
|
||||
sick,
|
||||
late,
|
||||
quitter,
|
||||
healthy,
|
||||
acceptable,
|
||||
fallible,
|
||||
average,
|
||||
diligent,
|
||||
cheater,
|
||||
npc
|
||||
}
|
@ -29,7 +29,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
// zero is one, ...
|
||||
List<Color> _gradeColors;
|
||||
bool _newsEnabled;
|
||||
int _newsState;
|
||||
String _seenNews;
|
||||
bool _notificationsEnabled;
|
||||
/*
|
||||
notificationsBitfield values:
|
||||
@ -69,6 +69,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
String _lastAccountId;
|
||||
bool _renamedSubjectsEnabled;
|
||||
bool _renamedSubjectsItalics;
|
||||
bool _renamedTeachersEnabled;
|
||||
bool _renamedTeachersItalics;
|
||||
Color _liveActivityColor;
|
||||
|
||||
SettingsProvider({
|
||||
DatabaseProvider? database,
|
||||
@ -79,7 +82,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
required AccentColor accentColor,
|
||||
required List<Color> gradeColors,
|
||||
required bool newsEnabled,
|
||||
required int newsState,
|
||||
required String seenNews,
|
||||
required bool notificationsEnabled,
|
||||
required int notificationsBitfield,
|
||||
required bool developerMode,
|
||||
@ -106,6 +109,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
required String lastAccountId,
|
||||
required bool renameSubjectsEnabled,
|
||||
required bool renameSubjectsItalics,
|
||||
required bool renameTeachersEnabled,
|
||||
required bool renameTeachersItalics,
|
||||
required Color liveActivityColor,
|
||||
}) : _database = database,
|
||||
_language = language,
|
||||
_startPage = startPage,
|
||||
@ -114,7 +120,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
_accentColor = accentColor,
|
||||
_gradeColors = gradeColors,
|
||||
_newsEnabled = newsEnabled,
|
||||
_newsState = newsState,
|
||||
_seenNews = seenNews,
|
||||
_notificationsEnabled = notificationsEnabled,
|
||||
_notificationsBitfield = notificationsBitfield,
|
||||
_developerMode = developerMode,
|
||||
@ -140,7 +146,10 @@ class SettingsProvider extends ChangeNotifier {
|
||||
_premiumLogin = premiumLogin,
|
||||
_lastAccountId = lastAccountId,
|
||||
_renamedSubjectsEnabled = renameSubjectsEnabled,
|
||||
_renamedSubjectsItalics = renameSubjectsItalics;
|
||||
_renamedSubjectsItalics = renameSubjectsItalics,
|
||||
_renamedTeachersEnabled = renameTeachersEnabled,
|
||||
_renamedTeachersItalics = renameTeachersItalics,
|
||||
_liveActivityColor = liveActivityColor;
|
||||
|
||||
factory SettingsProvider.fromMap(Map map,
|
||||
{required DatabaseProvider database}) {
|
||||
@ -167,7 +176,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
Color(map["grade_color5"]),
|
||||
],
|
||||
newsEnabled: map["news"] == 1,
|
||||
newsState: map["news_state"],
|
||||
seenNews: map["seen_news"],
|
||||
notificationsEnabled: map["notifications"] == 1,
|
||||
notificationsBitfield: map["notifications_bitfield"],
|
||||
notificationPollInterval: map["notification_poll_interval"],
|
||||
@ -194,7 +203,10 @@ class SettingsProvider extends ChangeNotifier {
|
||||
premiumLogin: map["premium_login"],
|
||||
lastAccountId: map["last_account_id"],
|
||||
renameSubjectsEnabled: map["renamed_subjects_enabled"] == 1,
|
||||
renameSubjectsItalics: map["renamed_subjects_italics"] == 0,
|
||||
renameSubjectsItalics: map["renamed_subjects_italics"] == 1,
|
||||
renameTeachersEnabled: map["renamed_teachers_enabled"] == 1,
|
||||
renameTeachersItalics: map["renamed_teachers_italics"] == 1,
|
||||
liveActivityColor: Color(map["live_activity_color"]),
|
||||
);
|
||||
}
|
||||
|
||||
@ -206,7 +218,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
"theme": _theme.index,
|
||||
"accent_color": _accentColor.index,
|
||||
"news": _newsEnabled ? 1 : 0,
|
||||
"news_state": _newsState,
|
||||
"seen_news": _seenNews,
|
||||
"notifications": _notificationsEnabled ? 1 : 0,
|
||||
"notifications_bitfield": _notificationsBitfield,
|
||||
"developer_mode": _developerMode ? 1 : 0,
|
||||
@ -236,7 +248,10 @@ class SettingsProvider extends ChangeNotifier {
|
||||
"premium_login": _premiumLogin,
|
||||
"last_account_id": _lastAccountId,
|
||||
"renamed_subjects_enabled": _renamedSubjectsEnabled ? 1 : 0,
|
||||
"renamed_subjects_italics": _renamedSubjectsItalics ? 1 : 0
|
||||
"renamed_subjects_italics": _renamedSubjectsItalics ? 1 : 0,
|
||||
"renamed_teachers_enabled": _renamedTeachersEnabled ? 1 : 0,
|
||||
"renamed_teachers_italics": _renamedTeachersItalics ? 1 : 0,
|
||||
"live_activity_color": _liveActivityColor.value,
|
||||
};
|
||||
}
|
||||
|
||||
@ -256,7 +271,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
DarkMobileAppColors().gradeFive,
|
||||
],
|
||||
newsEnabled: true,
|
||||
newsState: -1,
|
||||
seenNews: '',
|
||||
notificationsEnabled: true,
|
||||
notificationsBitfield: 255,
|
||||
developerMode: false,
|
||||
@ -283,6 +298,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
lastAccountId: "",
|
||||
renameSubjectsEnabled: false,
|
||||
renameSubjectsItalics: false,
|
||||
renameTeachersEnabled: false,
|
||||
renameTeachersItalics: false,
|
||||
liveActivityColor: const Color(0xFF676767),
|
||||
);
|
||||
}
|
||||
|
||||
@ -294,7 +312,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
AccentColor get accentColor => _accentColor;
|
||||
List<Color> get gradeColors => _gradeColors;
|
||||
bool get newsEnabled => _newsEnabled;
|
||||
int get newsState => _newsState;
|
||||
List<String> get seenNews => _seenNews.split(',');
|
||||
bool get notificationsEnabled => _notificationsEnabled;
|
||||
int get notificationsBitfield => _notificationsBitfield;
|
||||
bool get developerMode => _developerMode;
|
||||
@ -324,6 +342,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
String get lastAccountId => _lastAccountId;
|
||||
bool get renamedSubjectsEnabled => _renamedSubjectsEnabled;
|
||||
bool get renamedSubjectsItalics => _renamedSubjectsItalics;
|
||||
bool get renamedTeachersEnabled => _renamedTeachersEnabled;
|
||||
bool get renamedTeachersItalics => _renamedTeachersItalics;
|
||||
Color get liveActivityColor => _liveActivityColor;
|
||||
|
||||
Future<void> update({
|
||||
bool store = true,
|
||||
@ -334,7 +355,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
AccentColor? accentColor,
|
||||
List<Color>? gradeColors,
|
||||
bool? newsEnabled,
|
||||
int? newsState,
|
||||
String? seenNewsId,
|
||||
bool? notificationsEnabled,
|
||||
int? notificationsBitfield,
|
||||
bool? developerMode,
|
||||
@ -361,71 +382,111 @@ class SettingsProvider extends ChangeNotifier {
|
||||
String? lastAccountId,
|
||||
bool? renamedSubjectsEnabled,
|
||||
bool? renamedSubjectsItalics,
|
||||
bool? renamedTeachersEnabled,
|
||||
bool? renamedTeachersItalics,
|
||||
Color? liveActivityColor,
|
||||
}) async {
|
||||
if (language != null && language != _language) _language = language;
|
||||
if (startPage != null && startPage != _startPage) _startPage = startPage;
|
||||
if (rounding != null && rounding != _rounding) _rounding = rounding;
|
||||
if (theme != null && theme != _theme) _theme = theme;
|
||||
if (accentColor != null && accentColor != _accentColor)
|
||||
if (accentColor != null && accentColor != _accentColor) {
|
||||
_accentColor = accentColor;
|
||||
if (gradeColors != null && gradeColors != _gradeColors)
|
||||
}
|
||||
if (gradeColors != null && gradeColors != _gradeColors) {
|
||||
_gradeColors = gradeColors;
|
||||
if (newsEnabled != null && newsEnabled != _newsEnabled)
|
||||
}
|
||||
if (newsEnabled != null && newsEnabled != _newsEnabled) {
|
||||
_newsEnabled = newsEnabled;
|
||||
if (newsState != null && newsState != _newsState) _newsState = newsState;
|
||||
}
|
||||
if (seenNewsId != null && !_seenNews.split(',').contains(seenNewsId)) {
|
||||
var tempList = _seenNews.split(',');
|
||||
tempList.add(seenNewsId);
|
||||
_seenNews = tempList.join(',');
|
||||
}
|
||||
if (notificationsEnabled != null &&
|
||||
notificationsEnabled != _notificationsEnabled)
|
||||
notificationsEnabled != _notificationsEnabled) {
|
||||
_notificationsEnabled = notificationsEnabled;
|
||||
}
|
||||
if (notificationsBitfield != null &&
|
||||
notificationsBitfield != _notificationsBitfield)
|
||||
notificationsBitfield != _notificationsBitfield) {
|
||||
_notificationsBitfield = notificationsBitfield;
|
||||
if (developerMode != null && developerMode != _developerMode)
|
||||
}
|
||||
if (developerMode != null && developerMode != _developerMode) {
|
||||
_developerMode = developerMode;
|
||||
}
|
||||
if (notificationPollInterval != null &&
|
||||
notificationPollInterval != _notificationPollInterval) {
|
||||
_notificationPollInterval = notificationPollInterval;
|
||||
}
|
||||
if (vibrate != null && vibrate != _vibrate) _vibrate = vibrate;
|
||||
if (abWeeks != null && abWeeks != _abWeeks) _abWeeks = abWeeks;
|
||||
if (swapABweeks != null && swapABweeks != _swapABweeks)
|
||||
if (swapABweeks != null && swapABweeks != _swapABweeks) {
|
||||
_swapABweeks = swapABweeks;
|
||||
if (updateChannel != null && updateChannel != _updateChannel)
|
||||
}
|
||||
if (updateChannel != null && updateChannel != _updateChannel) {
|
||||
_updateChannel = updateChannel;
|
||||
}
|
||||
if (config != null && config != _config) _config = config;
|
||||
if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId;
|
||||
if (graphClassAvg != null && graphClassAvg != _graphClassAvg)
|
||||
if (graphClassAvg != null && graphClassAvg != _graphClassAvg) {
|
||||
_graphClassAvg = graphClassAvg;
|
||||
}
|
||||
if (goodStudent != null) _goodStudent = goodStudent;
|
||||
if (presentationMode != null && presentationMode != _presentationMode)
|
||||
if (presentationMode != null && presentationMode != _presentationMode) {
|
||||
_presentationMode = presentationMode;
|
||||
}
|
||||
if (bellDelay != null && bellDelay != _bellDelay) _bellDelay = bellDelay;
|
||||
if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled)
|
||||
if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled) {
|
||||
_bellDelayEnabled = bellDelayEnabled;
|
||||
if (gradeOpeningFun != null && gradeOpeningFun != _gradeOpeningFun)
|
||||
}
|
||||
if (gradeOpeningFun != null && gradeOpeningFun != _gradeOpeningFun) {
|
||||
_gradeOpeningFun = gradeOpeningFun;
|
||||
}
|
||||
if (iconPack != null && iconPack != _iconPack) _iconPack = iconPack;
|
||||
if (customAccentColor != null && customAccentColor != _customAccentColor)
|
||||
if (customAccentColor != null && customAccentColor != _customAccentColor) {
|
||||
_customAccentColor = customAccentColor;
|
||||
}
|
||||
if (customBackgroundColor != null &&
|
||||
customBackgroundColor != _customBackgroundColor)
|
||||
customBackgroundColor != _customBackgroundColor) {
|
||||
_customBackgroundColor = customBackgroundColor;
|
||||
}
|
||||
if (customHighlightColor != null &&
|
||||
customHighlightColor != _customHighlightColor)
|
||||
customHighlightColor != _customHighlightColor) {
|
||||
_customHighlightColor = customHighlightColor;
|
||||
if (premiumScopes != null && premiumScopes != _premiumScopes)
|
||||
}
|
||||
if (premiumScopes != null && premiumScopes != _premiumScopes) {
|
||||
_premiumScopes = premiumScopes;
|
||||
if (premiumAccessToken != null && premiumAccessToken != _premiumAccessToken)
|
||||
}
|
||||
if (premiumAccessToken != null &&
|
||||
premiumAccessToken != _premiumAccessToken) {
|
||||
_premiumAccessToken = premiumAccessToken;
|
||||
if (premiumLogin != null && premiumLogin != _premiumLogin)
|
||||
}
|
||||
if (premiumLogin != null && premiumLogin != _premiumLogin) {
|
||||
_premiumLogin = premiumLogin;
|
||||
if (lastAccountId != null && lastAccountId != _lastAccountId)
|
||||
}
|
||||
if (lastAccountId != null && lastAccountId != _lastAccountId) {
|
||||
_lastAccountId = lastAccountId;
|
||||
}
|
||||
if (renamedSubjectsEnabled != null &&
|
||||
renamedSubjectsEnabled != _renamedSubjectsEnabled)
|
||||
renamedSubjectsEnabled != _renamedSubjectsEnabled) {
|
||||
_renamedSubjectsEnabled = renamedSubjectsEnabled;
|
||||
}
|
||||
if (renamedSubjectsItalics != null &&
|
||||
renamedSubjectsItalics != _renamedSubjectsItalics)
|
||||
renamedSubjectsItalics != _renamedSubjectsItalics) {
|
||||
_renamedSubjectsItalics = renamedSubjectsItalics;
|
||||
}
|
||||
if (renamedTeachersEnabled != null &&
|
||||
renamedTeachersEnabled != _renamedTeachersEnabled) {
|
||||
_renamedTeachersEnabled = renamedTeachersEnabled;
|
||||
}
|
||||
if (renamedTeachersItalics != null &&
|
||||
renamedTeachersItalics != _renamedTeachersItalics) {
|
||||
_renamedTeachersItalics = renamedTeachersItalics;
|
||||
}
|
||||
if (liveActivityColor != null && liveActivityColor != _liveActivityColor) {
|
||||
_liveActivityColor = liveActivityColor;
|
||||
}
|
||||
if (store) await _database?.store.storeSettings(this);
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ class DarkMobileAppColors implements ThemeAppColors {
|
||||
final gradeTwo = const Color(0xFFAE3DF4);
|
||||
@override
|
||||
final gradeOne = const Color(0xFFF43DAB);
|
||||
@override
|
||||
final purple = const Color(0xffBF5AF2);
|
||||
@override
|
||||
final pink = const Color(0xffFF375F);
|
||||
|
@ -12,7 +12,8 @@ import 'package:filcnaplo_mobile_ui/common/widgets/lesson/changed_lesson_tile.da
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
// difference.inDays is not reliable
|
||||
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);
|
||||
bool _sameDate(DateTime a, DateTime b) =>
|
||||
(a.year == b.year && a.month == b.month && a.day == b.day);
|
||||
|
||||
List<Widget> sortDateWidgets(
|
||||
BuildContext context, {
|
||||
@ -35,13 +36,16 @@ List<Widget> sortDateWidgets(
|
||||
if (message.conversationId != null) {
|
||||
convMessages.add(w);
|
||||
|
||||
Conversation conv = conversations.firstWhere((e) => e.id == message.conversationId, orElse: () => Conversation(id: message.conversationId!));
|
||||
Conversation conv = conversations.firstWhere(
|
||||
(e) => e.id == message.conversationId,
|
||||
orElse: () => Conversation(id: message.conversationId!));
|
||||
conv.add(message);
|
||||
if (conv.messages.length == 1) conversations.add(conv);
|
||||
}
|
||||
|
||||
if (conversations.any((c) => c.id == message.messageId)) {
|
||||
Conversation conv = conversations.firstWhere((e) => e.id == message.messageId);
|
||||
Conversation conv =
|
||||
conversations.firstWhere((e) => e.id == message.messageId);
|
||||
convMessages.add(w);
|
||||
conv.add(message);
|
||||
}
|
||||
@ -87,26 +91,41 @@ List<Widget> sortDateWidgets(
|
||||
|
||||
// Group Absence Tiles
|
||||
List<DateWidget> absenceTileWidgets = elements.where((element) {
|
||||
return element.widget is AbsenceViewable && (element.widget as AbsenceViewable).absence.delay == 0;
|
||||
return element.widget is AbsenceViewable &&
|
||||
(element.widget as AbsenceViewable).absence.delay == 0;
|
||||
}).toList();
|
||||
List<AbsenceViewable> absenceTiles = absenceTileWidgets.map((e) => e.widget as AbsenceViewable).toList();
|
||||
List<AbsenceViewable> absenceTiles =
|
||||
absenceTileWidgets.map((e) => e.widget as AbsenceViewable).toList();
|
||||
if (absenceTiles.length > 1) {
|
||||
elements.removeWhere((element) => element.widget.runtimeType == AbsenceViewable && (element.widget as AbsenceViewable).absence.delay == 0);
|
||||
elements.removeWhere((element) =>
|
||||
element.widget.runtimeType == AbsenceViewable &&
|
||||
(element.widget as AbsenceViewable).absence.delay == 0);
|
||||
if (elements.isEmpty) {
|
||||
cst = false;
|
||||
}
|
||||
elements.add(DateWidget(
|
||||
widget: AbsenceGroupTile(absenceTiles, showDate: !cst),
|
||||
elements.add(
|
||||
DateWidget(
|
||||
widget: AbsenceGroupTile(
|
||||
absenceTiles,
|
||||
showDate: !cst,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
),
|
||||
date: absenceTileWidgets.first.date,
|
||||
key: "${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group"));
|
||||
key:
|
||||
"${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group"),
|
||||
);
|
||||
}
|
||||
|
||||
// Bring Lesson Tiles to front & sort by index asc
|
||||
List<DateWidget> lessonTiles = elements.where((element) {
|
||||
return element.widget.runtimeType == ChangedLessonTile;
|
||||
}).toList();
|
||||
lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile).lesson.lessonIndex.compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex));
|
||||
elements.removeWhere((element) => element.widget.runtimeType == ChangedLessonTile);
|
||||
lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile)
|
||||
.lesson
|
||||
.lessonIndex
|
||||
.compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex));
|
||||
elements.removeWhere(
|
||||
(element) => element.widget.runtimeType == ChangedLessonTile);
|
||||
elements.insertAll(0, lessonTiles);
|
||||
|
||||
final date = (elements + absenceTileWidgets).first.date;
|
||||
@ -122,7 +141,8 @@ List<Widget> sortDateWidgets(
|
||||
spawnIsolate: false,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, animation, item, index) => filterItemBuilder(context, animation, item.widget, index),
|
||||
itemBuilder: (context, animation, item, index) =>
|
||||
filterItemBuilder(context, animation, item.widget, index),
|
||||
items: elements,
|
||||
),
|
||||
),
|
||||
@ -131,9 +151,12 @@ List<Widget> sortDateWidgets(
|
||||
}
|
||||
|
||||
final nh = DateTime.now();
|
||||
final now = DateTime(nh.year, nh.month, nh.day).subtract(const Duration(seconds: 1));
|
||||
final now =
|
||||
DateTime(nh.year, nh.month, nh.day).subtract(const Duration(seconds: 1));
|
||||
|
||||
if (showDivider && items.any((i) => i.date.isBefore(now)) && items.any((i) => i.date.isAfter(now))) {
|
||||
if (showDivider &&
|
||||
items.any((i) => i.date.isBefore(now)) &&
|
||||
items.any((i) => i.date.isAfter(now))) {
|
||||
items.add(
|
||||
DateWidget(
|
||||
date: now,
|
||||
@ -153,7 +176,9 @@ List<Widget> sortDateWidgets(
|
||||
}
|
||||
|
||||
// Sort future dates asc, past dates desc
|
||||
items.sort((a, b) => (a.date.isAfter(now) && b.date.isAfter(now) ? 1 : -1) * a.date.compareTo(b.date));
|
||||
items.sort((a, b) =>
|
||||
(a.date.isAfter(now) && b.date.isAfter(now) ? 1 : -1) *
|
||||
a.date.compareTo(b.date));
|
||||
|
||||
return items.map((e) => e.widget).toList();
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class LessonTile extends StatelessWidget {
|
||||
fillLeading = true;
|
||||
}
|
||||
|
||||
if (lesson.substituteTeacher != "") {
|
||||
if (lesson.substituteTeacher?.name != "") {
|
||||
fill = true;
|
||||
accent = AppColors.of(context).yellow;
|
||||
}
|
||||
@ -113,7 +113,7 @@ class LessonTile extends StatelessWidget {
|
||||
if (lesson.isChanged) {
|
||||
if (lesson.status?.name == "Elmaradt") {
|
||||
description = 'cancelled'.i18n;
|
||||
} else if (lesson.substituteTeacher != "") {
|
||||
} else if (lesson.substituteTeacher?.name != "") {
|
||||
description = 'substitution'.i18n;
|
||||
}
|
||||
}
|
||||
@ -161,8 +161,10 @@ class LessonTile extends StatelessWidget {
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(!lesson.isEmpty ? 1.0 : 0.5),
|
||||
fontStyle:
|
||||
lesson.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null),
|
||||
fontStyle: lesson.subject.isRenamed &&
|
||||
settingsProvider.renamedSubjectsItalics
|
||||
? FontStyle.italic
|
||||
: null),
|
||||
),
|
||||
subtitle: description != ""
|
||||
? Text(
|
||||
|
@ -1,23 +0,0 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <flutter_acrylic/flutter_acrylic_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin");
|
||||
flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
@ -1,26 +0,0 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
flutter_acrylic
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
@ -1,28 +0,0 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
import dynamic_color
|
||||
import flutter_local_notifications
|
||||
import macos_window_utils
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import sqflite
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
PODS:
|
||||
- connectivity_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift
|
||||
- dynamic_color (0.0.2):
|
||||
- FlutterMacOS
|
||||
- flutter_acrylic (0.1.0):
|
||||
- FlutterMacOS
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- share_plus_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqflite (0.0.2):
|
||||
- FlutterMacOS
|
||||
- FMDB (>= 2.7.5)
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
|
||||
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
|
||||
- flutter_acrylic (from `Flutter/ephemeral/.symlinks/plugins/flutter_acrylic/macos`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- FMDB
|
||||
- ReachabilitySwift
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
connectivity_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||
dynamic_color:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
|
||||
flutter_acrylic:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_acrylic/macos
|
||||
flutter_local_notifications:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
share_plus_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
|
||||
sqflite:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
|
||||
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
||||
flutter_acrylic: c3df24ae52ab6597197837ce59ef2a8542640c17
|
||||
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
|
||||
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
||||
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
|
||||
|
||||
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
|
||||
|
||||
COCOAPODS: 1.12.1
|
@ -3,7 +3,7 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez"
|
||||
homepage: https://refilc.hu
|
||||
publish_to: "none"
|
||||
|
||||
version: 4.0.4+212
|
||||
version: 4.1.1+216
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
@ -48,7 +48,7 @@ dependencies:
|
||||
crypto: ^3.0.2
|
||||
elegant_notification: ^1.6.1
|
||||
flutter_feather_icons: ^2.0.0+1
|
||||
live_activities: ^1.0.0
|
||||
live_activities: ^1.7.4
|
||||
animated_flip_counter: ^0.2.5
|
||||
lottie: ^1.4.3
|
||||
rive: ^0.9.1
|
||||
@ -65,6 +65,12 @@ dependencies:
|
||||
background_fetch: ^1.1.5
|
||||
flutter_local_notifications: ^14.1.0
|
||||
package_info_plus: ^4.0.2
|
||||
screenshot: ^2.1.0
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
sqflite_common_ffi_web: ^0.4.0
|
||||
image_crop:
|
||||
git:
|
||||
url: https://github.com/kimaah/image_crop.git
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
@ -80,6 +86,7 @@ flutter:
|
||||
- assets/icons/ic_splash.png
|
||||
- assets/animations/
|
||||
- assets/images/
|
||||
- assets/images/subject_covers/
|
||||
|
||||
fonts:
|
||||
- family: FilcIcons
|
||||
|
30
filcnaplo/test/widget_test.dart
Normal file
@ -0,0 +1,30 @@
|
||||
// // This is a basic Flutter widget test.
|
||||
// //
|
||||
// // To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// // utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// // gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// // tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// import 'package:filcnaplo/main.dart';
|
||||
|
||||
// void main() {
|
||||
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// // Build our app and trigger a frame.
|
||||
// await tester.pumpWidget(const MyApp());
|
||||
|
||||
// // Verify that our counter starts at 0.
|
||||
// expect(find.text('0'), findsOneWidget);
|
||||
// expect(find.text('1'), findsNothing);
|
||||
|
||||
// // Tap the '+' icon and trigger a frame.
|
||||
// await tester.tap(find.byIcon(Icons.add));
|
||||
// await tester.pump();
|
||||
|
||||
// // Verify that our counter has incremented.
|
||||
// expect(find.text('0'), findsNothing);
|
||||
// expect(find.text('1'), findsOneWidget);
|
||||
// });
|
||||
// }
|
BIN
filcnaplo/web/favicon.png
Normal file
After Width: | Height: | Size: 917 B |
BIN
filcnaplo/web/icons/Icon-192.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
filcnaplo/web/icons/Icon-512.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
filcnaplo/web/icons/Icon-maskable-192.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
filcnaplo/web/icons/Icon-maskable-512.png
Normal file
After Width: | Height: | Size: 20 KiB |
59
filcnaplo/web/index.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="filcnaplo">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>filcnaplo</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<script>
|
||||
// The value below is injected by flutter build, do not touch.
|
||||
var serviceWorkerVersion = null;
|
||||
</script>
|
||||
<!-- This script adds the flutter initialization JS code -->
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener('load', function(ev) {
|
||||
// Download main.dart.js
|
||||
_flutter.loader.loadEntrypoint({
|
||||
serviceWorker: {
|
||||
serviceWorkerVersion: serviceWorkerVersion,
|
||||
},
|
||||
onEntrypointLoaded: function(engineInitializer) {
|
||||
engineInitializer.initializeEngine().then(function(appRunner) {
|
||||
appRunner.runApp();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
35
filcnaplo/web/manifest.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "filcnaplo",
|
||||
"short_name": "filcnaplo",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "A new Flutter project.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
17
filcnaplo/windows/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
flutter/ephemeral/
|
||||
|
||||
# Visual Studio user-specific files.
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Visual Studio build-related files.
|
||||
x64/
|
||||
x86/
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
121
filcnaplo/windows/runner/Runner.rc
Normal file
@ -0,0 +1,121 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
||||
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
||||
#else
|
||||
#define VERSION_AS_NUMBER 1,0,0,0
|
||||
#endif
|
||||
|
||||
#if defined(FLUTTER_VERSION)
|
||||
#define VERSION_AS_STRING FLUTTER_VERSION
|
||||
#else
|
||||
#define VERSION_AS_STRING "1.0.0"
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_AS_NUMBER
|
||||
PRODUCTVERSION VERSION_AS_NUMBER
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "hu.refilc" "\0"
|
||||
VALUE "FileDescription", "filcnaplo" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "filcnaplo" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2023 hu.refilc. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "filcnaplo.exe" "\0"
|
||||
VALUE "ProductName", "filcnaplo" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
66
filcnaplo/windows/runner/flutter_window.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include "flutter_window.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||
: project_(project) {}
|
||||
|
||||
FlutterWindow::~FlutterWindow() {}
|
||||
|
||||
bool FlutterWindow::OnCreate() {
|
||||
if (!Win32Window::OnCreate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
// The size here must match the window dimensions to avoid unnecessary surface
|
||||
// creation / destruction in the startup path.
|
||||
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||
// Ensure that basic setup of the controller was successful.
|
||||
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||
return false;
|
||||
}
|
||||
RegisterPlugins(flutter_controller_->engine());
|
||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||
|
||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||
this->Show();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlutterWindow::OnDestroy() {
|
||||
if (flutter_controller_) {
|
||||
flutter_controller_ = nullptr;
|
||||
}
|
||||
|
||||
Win32Window::OnDestroy();
|
||||
}
|
||||
|
||||
LRESULT
|
||||
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
// Give Flutter, including plugins, an opportunity to handle window messages.
|
||||
if (flutter_controller_) {
|
||||
std::optional<LRESULT> result =
|
||||
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||
lparam);
|
||||
if (result) {
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_FONTCHANGE:
|
||||
flutter_controller_->engine()->ReloadSystemFonts();
|
||||
break;
|
||||
}
|
||||
|
||||
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||
}
|
33
filcnaplo/windows/runner/flutter_window.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||
#define RUNNER_FLUTTER_WINDOW_H_
|
||||
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "win32_window.h"
|
||||
|
||||
// A window that does nothing but host a Flutter view.
|
||||
class FlutterWindow : public Win32Window {
|
||||
public:
|
||||
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
||||
explicit FlutterWindow(const flutter::DartProject& project);
|
||||
virtual ~FlutterWindow();
|
||||
|
||||
protected:
|
||||
// Win32Window:
|
||||
bool OnCreate() override;
|
||||
void OnDestroy() override;
|
||||
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept override;
|
||||
|
||||
private:
|
||||
// The project to run.
|
||||
flutter::DartProject project_;
|
||||
|
||||
// The Flutter instance hosted by this window.
|
||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||
};
|
||||
|
||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
43
filcnaplo/windows/runner/main.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
CreateAndAttachConsole();
|
||||
}
|
||||
|
||||
// Initialize COM, so that it is available for use in the library and/or
|
||||
// plugins.
|
||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
flutter::DartProject project(L"data");
|
||||
|
||||
std::vector<std::string> command_line_arguments =
|
||||
GetCommandLineArguments();
|
||||
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(800, 720);
|
||||
if (!window.Create(L"reFilc", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
::MSG msg;
|
||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||
::TranslateMessage(&msg);
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
::CoUninitialize();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
16
filcnaplo/windows/runner/resource.h
Normal file
@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Runner.rc
|
||||
//
|
||||
#define IDI_APP_ICON 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
BIN
filcnaplo/windows/runner/resources/app_icon.ico
Normal file
After Width: | Height: | Size: 25 KiB |
20
filcnaplo/windows/runner/runner.exe.manifest
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 and Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
65
filcnaplo/windows/runner/utils.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void CreateAndAttachConsole() {
|
||||
if (::AllocConsole()) {
|
||||
FILE *unused;
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||
_dup2(_fileno(stdout), 1);
|
||||
}
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||
_dup2(_fileno(stdout), 2);
|
||||
}
|
||||
std::ios::sync_with_stdio();
|
||||
FlutterDesktopResyncOutputStreams();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> GetCommandLineArguments() {
|
||||
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
||||
int argc;
|
||||
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
if (argv == nullptr) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::vector<std::string> command_line_arguments;
|
||||
|
||||
// Skip the first argument as it's the binary name.
|
||||
for (int i = 1; i < argc; i++) {
|
||||
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
||||
}
|
||||
|
||||
::LocalFree(argv);
|
||||
|
||||
return command_line_arguments;
|
||||
}
|
||||
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
||||
if (utf16_string == nullptr) {
|
||||
return std::string();
|
||||
}
|
||||
int target_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
-1, nullptr, 0, nullptr, nullptr)
|
||||
-1; // remove the trailing null character
|
||||
int input_length = (int)wcslen(utf16_string);
|
||||
std::string utf8_string;
|
||||
if (target_length <= 0 || target_length > utf8_string.max_size()) {
|
||||
return utf8_string;
|
||||
}
|
||||
utf8_string.resize(target_length);
|
||||
int converted_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
input_length, utf8_string.data(), target_length, nullptr, nullptr);
|
||||
if (converted_length == 0) {
|
||||
return std::string();
|
||||
}
|
||||
return utf8_string;
|
||||
}
|
19
filcnaplo/windows/runner/utils.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef RUNNER_UTILS_H_
|
||||
#define RUNNER_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Creates a console for the process, and redirects stdout and stderr to
|
||||
// it for both the runner and the Flutter library.
|
||||
void CreateAndAttachConsole();
|
||||
|
||||
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
||||
// encoded in UTF-8. Returns an empty std::string on failure.
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
||||
|
||||
// Gets the command line arguments passed in as a std::vector<std::string>,
|
||||
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
||||
std::vector<std::string> GetCommandLineArguments();
|
||||
|
||||
#endif // RUNNER_UTILS_H_
|
288
filcnaplo/windows/runner/win32_window.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
#include "win32_window.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <flutter_windows.h>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/// Window attribute that enables dark mode window decorations.
|
||||
///
|
||||
/// Redefined in case the developer's machine has a Windows SDK older than
|
||||
/// version 10.0.22000.0.
|
||||
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||
#endif
|
||||
|
||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||
|
||||
/// Registry key for app theme preference.
|
||||
///
|
||||
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
|
||||
/// value indicates apps should use light mode.
|
||||
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||||
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
|
||||
|
||||
// The number of Win32Window objects that currently exist.
|
||||
static int g_active_window_count = 0;
|
||||
|
||||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||
|
||||
// Scale helper to convert logical scaler values to physical using passed in
|
||||
// scale factor
|
||||
int Scale(int source, double scale_factor) {
|
||||
return static_cast<int>(source * scale_factor);
|
||||
}
|
||||
|
||||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||
// This API is only needed for PerMonitor V1 awareness mode.
|
||||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||
if (!user32_module) {
|
||||
return;
|
||||
}
|
||||
auto enable_non_client_dpi_scaling =
|
||||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||
if (enable_non_client_dpi_scaling != nullptr) {
|
||||
enable_non_client_dpi_scaling(hwnd);
|
||||
}
|
||||
FreeLibrary(user32_module);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Manages the Win32Window's window class registration.
|
||||
class WindowClassRegistrar {
|
||||
public:
|
||||
~WindowClassRegistrar() = default;
|
||||
|
||||
// Returns the singleton registrar instance.
|
||||
static WindowClassRegistrar* GetInstance() {
|
||||
if (!instance_) {
|
||||
instance_ = new WindowClassRegistrar();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
// Returns the name of the window class, registering the class if it hasn't
|
||||
// previously been registered.
|
||||
const wchar_t* GetWindowClass();
|
||||
|
||||
// Unregisters the window class. Should only be called if there are no
|
||||
// instances of the window.
|
||||
void UnregisterWindowClass();
|
||||
|
||||
private:
|
||||
WindowClassRegistrar() = default;
|
||||
|
||||
static WindowClassRegistrar* instance_;
|
||||
|
||||
bool class_registered_ = false;
|
||||
};
|
||||
|
||||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||
|
||||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||
if (!class_registered_) {
|
||||
WNDCLASS window_class{};
|
||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
window_class.lpszClassName = kWindowClassName;
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.cbClsExtra = 0;
|
||||
window_class.cbWndExtra = 0;
|
||||
window_class.hInstance = GetModuleHandle(nullptr);
|
||||
window_class.hIcon =
|
||||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||
window_class.hbrBackground = 0;
|
||||
window_class.lpszMenuName = nullptr;
|
||||
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||
RegisterClass(&window_class);
|
||||
class_registered_ = true;
|
||||
}
|
||||
return kWindowClassName;
|
||||
}
|
||||
|
||||
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||
UnregisterClass(kWindowClassName, nullptr);
|
||||
class_registered_ = false;
|
||||
}
|
||||
|
||||
Win32Window::Win32Window() {
|
||||
++g_active_window_count;
|
||||
}
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
--g_active_window_count;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool Win32Window::Create(const std::wstring& title,
|
||||
const Point& origin,
|
||||
const Size& size) {
|
||||
Destroy();
|
||||
|
||||
const wchar_t* window_class =
|
||||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||
|
||||
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||
static_cast<LONG>(origin.y)};
|
||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
HWND window = CreateWindow(
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
|
||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||
|
||||
if (!window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateTheme(window);
|
||||
|
||||
return OnCreate();
|
||||
}
|
||||
|
||||
bool Win32Window::Show() {
|
||||
return ShowWindow(window_handle_, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
if (message == WM_NCCREATE) {
|
||||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||
|
||||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||
EnableFullDpiSupportIfAvailable(window);
|
||||
that->window_handle_ = window;
|
||||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||
return that->MessageHandler(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT
|
||||
Win32Window::MessageHandler(HWND hwnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
window_handle_ = nullptr;
|
||||
Destroy();
|
||||
if (quit_on_close_) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||
|
||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
RECT rect = GetClientArea();
|
||||
if (child_content_ != nullptr) {
|
||||
// Size and position the child window.
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, TRUE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_ACTIVATE:
|
||||
if (child_content_ != nullptr) {
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
||||
UpdateTheme(hwnd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void Win32Window::Destroy() {
|
||||
OnDestroy();
|
||||
|
||||
if (window_handle_) {
|
||||
DestroyWindow(window_handle_);
|
||||
window_handle_ = nullptr;
|
||||
}
|
||||
if (g_active_window_count == 0) {
|
||||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||
}
|
||||
}
|
||||
|
||||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||
return reinterpret_cast<Win32Window*>(
|
||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
void Win32Window::SetChildContent(HWND content) {
|
||||
child_content_ = content;
|
||||
SetParent(content, window_handle_);
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||
frame.bottom - frame.top, true);
|
||||
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
|
||||
RECT Win32Window::GetClientArea() {
|
||||
RECT frame;
|
||||
GetClientRect(window_handle_, &frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
HWND Win32Window::GetHandle() {
|
||||
return window_handle_;
|
||||
}
|
||||
|
||||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||
quit_on_close_ = quit_on_close;
|
||||
}
|
||||
|
||||
bool Win32Window::OnCreate() {
|
||||
// No-op; provided for subclasses.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32Window::OnDestroy() {
|
||||
// No-op; provided for subclasses.
|
||||
}
|
||||
|
||||
void Win32Window::UpdateTheme(HWND const window) {
|
||||
DWORD light_mode;
|
||||
DWORD light_mode_size = sizeof(light_mode);
|
||||
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
|
||||
kGetPreferredBrightnessRegValue,
|
||||
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
||||
&light_mode_size);
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
BOOL enable_dark_mode = light_mode == 0;
|
||||
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
&enable_dark_mode, sizeof(enable_dark_mode));
|
||||
}
|
||||
}
|
102
filcnaplo/windows/runner/win32_window.h
Normal file
@ -0,0 +1,102 @@
|
||||
#ifndef RUNNER_WIN32_WINDOW_H_
|
||||
#define RUNNER_WIN32_WINDOW_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
||||
// inherited from by classes that wish to specialize with custom
|
||||
// rendering and input handling
|
||||
class Win32Window {
|
||||
public:
|
||||
struct Point {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
struct Size {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
Size(unsigned int width, unsigned int height)
|
||||
: width(width), height(height) {}
|
||||
};
|
||||
|
||||
Win32Window();
|
||||
virtual ~Win32Window();
|
||||
|
||||
// Creates a win32 window with |title| that is positioned and sized using
|
||||
// |origin| and |size|. New windows are created on the default monitor. Window
|
||||
// sizes are specified to the OS in physical pixels, hence to ensure a
|
||||
// consistent size this function will scale the inputted width and height as
|
||||
// as appropriate for the default monitor. The window is invisible until
|
||||
// |Show| is called. Returns true if the window was created successfully.
|
||||
bool Create(const std::wstring& title, const Point& origin, const Size& size);
|
||||
|
||||
// Show the current window. Returns true if the window was successfully shown.
|
||||
bool Show();
|
||||
|
||||
// Release OS resources associated with window.
|
||||
void Destroy();
|
||||
|
||||
// Inserts |content| into the window tree.
|
||||
void SetChildContent(HWND content);
|
||||
|
||||
// Returns the backing Window handle to enable clients to set icon and other
|
||||
// window properties. Returns nullptr if the window has been destroyed.
|
||||
HWND GetHandle();
|
||||
|
||||
// If true, closing this window will quit the application.
|
||||
void SetQuitOnClose(bool quit_on_close);
|
||||
|
||||
// Return a RECT representing the bounds of the current client area.
|
||||
RECT GetClientArea();
|
||||
|
||||
protected:
|
||||
// Processes and route salient window messages for mouse handling,
|
||||
// size change and DPI. Delegates handling of these to member overloads that
|
||||
// inheriting classes can handle.
|
||||
virtual LRESULT MessageHandler(HWND window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Called when CreateAndShow is called, allowing subclass window-related
|
||||
// setup. Subclasses should return false if setup fails.
|
||||
virtual bool OnCreate();
|
||||
|
||||
// Called when Destroy is called.
|
||||
virtual void OnDestroy();
|
||||
|
||||
private:
|
||||
friend class WindowClassRegistrar;
|
||||
|
||||
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
||||
// is passed when the non-client area is being created and enables automatic
|
||||
// non-client DPI scaling so that the non-client area automatically
|
||||
// responds to changes in DPI. All other messages are handled by
|
||||
// MessageHandler.
|
||||
static LRESULT CALLBACK WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Retrieves a class instance pointer for |window|
|
||||
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
||||
|
||||
// Update the window frame's theme to match the system theme.
|
||||
static void UpdateTheme(HWND const window);
|
||||
|
||||
bool quit_on_close_ = false;
|
||||
|
||||
// window handle for top level window.
|
||||
HWND window_handle_ = nullptr;
|
||||
|
||||
// window handle for hosted content.
|
||||
HWND child_content_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // RUNNER_WIN32_WINDOW_H_
|
@ -35,7 +35,8 @@ class SubjectAbsence {
|
||||
List<Absence> absences;
|
||||
double percentage;
|
||||
|
||||
SubjectAbsence({required this.subject, this.absences = const [], this.percentage = 0.0});
|
||||
SubjectAbsence(
|
||||
{required this.subject, this.absences = const [], this.percentage = 0.0});
|
||||
}
|
||||
|
||||
class AbsencesPage extends StatefulWidget {
|
||||
@ -45,7 +46,8 @@ class AbsencesPage extends StatefulWidget {
|
||||
_AbsencesPageState createState() => _AbsencesPageState();
|
||||
}
|
||||
|
||||
class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMixin {
|
||||
class _AbsencesPageState extends State<AbsencesPage>
|
||||
with TickerProviderStateMixin {
|
||||
late UserProvider user;
|
||||
late AbsenceProvider absenceProvider;
|
||||
late TimetableProvider timetableProvider;
|
||||
@ -65,7 +67,9 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
for (final lesson in timetableProvider.getWeek(Week.current()) ?? []) {
|
||||
if (!lesson.isEmpty && lesson.subject.id != '' && lesson.lessonYearIndex != null) {
|
||||
if (!lesson.isEmpty &&
|
||||
lesson.subject.id != '' &&
|
||||
lesson.lessonYearIndex != null) {
|
||||
_lessonCount.update(
|
||||
lesson.subject,
|
||||
(value) {
|
||||
@ -89,25 +93,30 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
if (absence.delay != 0) continue;
|
||||
|
||||
if (!_absences.containsKey(absence.subject)) {
|
||||
_absences[absence.subject] = SubjectAbsence(subject: absence.subject, absences: [absence]);
|
||||
_absences[absence.subject] =
|
||||
SubjectAbsence(subject: absence.subject, absences: [absence]);
|
||||
} else {
|
||||
_absences[absence.subject]?.absences.add(absence);
|
||||
}
|
||||
}
|
||||
|
||||
_absences.forEach((subject, absence) {
|
||||
final absentLessonsOfSubject = absenceProvider.absences.where((e) => e.subject == subject && e.delay == 0).length;
|
||||
final absentLessonsOfSubject = absenceProvider.absences
|
||||
.where((e) => e.subject == subject && e.delay == 0)
|
||||
.length;
|
||||
final totalLessonsOfSubject = _lessonCount[subject]?.lessonYearIndex ?? 0;
|
||||
|
||||
double absentLessonsOfSubjectPercentage;
|
||||
|
||||
if (absentLessonsOfSubject <= totalLessonsOfSubject) {
|
||||
absentLessonsOfSubjectPercentage = absentLessonsOfSubject / totalLessonsOfSubject * 100;
|
||||
absentLessonsOfSubjectPercentage =
|
||||
absentLessonsOfSubject / totalLessonsOfSubject * 100;
|
||||
} else {
|
||||
absentLessonsOfSubjectPercentage = -1;
|
||||
}
|
||||
|
||||
_absences[subject]?.percentage = absentLessonsOfSubjectPercentage.clamp(-1, 100.0);
|
||||
_absences[subject]?.percentage =
|
||||
absentLessonsOfSubjectPercentage.clamp(-1, 100.0);
|
||||
});
|
||||
|
||||
absences = _absences.values.toList();
|
||||
@ -131,7 +140,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: NestedScrollView(
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
physics: const BouncingScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics()),
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
@ -145,7 +155,10 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
"Absences".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text, fontSize: 32.0, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontSize: 32.0,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
bottom: FilterBar(items: [
|
||||
@ -158,7 +171,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
body: TabBarView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: _tabController,
|
||||
children: List.generate(3, (index) => filterViewBuilder(context, index))),
|
||||
children: List.generate(
|
||||
3, (index) => filterViewBuilder(context, index))),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -174,10 +188,17 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
widget: AbsenceSubjectTile(
|
||||
a.subject,
|
||||
percentage: a.percentage,
|
||||
excused: a.absences.where((a) => a.state == Justification.excused).length,
|
||||
unexcused: a.absences.where((a) => a.state == Justification.unexcused).length,
|
||||
pending: a.absences.where((a) => a.state == Justification.pending).length,
|
||||
onTap: () => AbsenceSubjectView.show(a.subject, a.absences, context: context),
|
||||
excused: a.absences
|
||||
.where((a) => a.state == Justification.excused)
|
||||
.length,
|
||||
unexcused: a.absences
|
||||
.where((a) => a.state == Justification.unexcused)
|
||||
.length,
|
||||
pending: a.absences
|
||||
.where((a) => a.state == Justification.pending)
|
||||
.length,
|
||||
onTap: () => AbsenceSubjectView.show(a.subject, a.absences,
|
||||
context: context),
|
||||
),
|
||||
));
|
||||
}
|
||||
@ -187,14 +208,17 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
if (absence.delay != 0) {
|
||||
items.add(DateWidget(
|
||||
date: absence.date,
|
||||
widget: AbsenceViewable(absence, padding: EdgeInsets.zero),
|
||||
));
|
||||
widget: AbsenceViewable(
|
||||
absence,
|
||||
padding: EdgeInsets.zero,
|
||||
)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AbsenceFilter.misses:
|
||||
for (var note in noteProvider.notes) {
|
||||
if (note.type?.name == "HaziFeladatHiany" || note.type?.name == "Felszereleshiany") {
|
||||
if (note.type?.name == "HaziFeladatHiany" ||
|
||||
note.type?.name == "Felszereleshiany") {
|
||||
items.add(DateWidget(
|
||||
date: note.date,
|
||||
widget: MissTile(note),
|
||||
@ -232,10 +256,15 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
title: Text("attention".i18n),
|
||||
content: Text("attention_body".i18n),
|
||||
actions: [ActionButton(label: "Ok", onTap: () => Navigator.of(context).pop())],
|
||||
actions: [
|
||||
ActionButton(
|
||||
label: "Ok",
|
||||
onTap: () => Navigator.of(context).pop())
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -262,7 +291,10 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: getFilterWidgets(AbsenceFilter.values[activeData]).map((e) => e.widget).cast<Widget>().toList(),
|
||||
children: getFilterWidgets(AbsenceFilter.values[activeData])
|
||||
.map((e) => e.widget)
|
||||
.cast<Widget>()
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -284,7 +316,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
itemCount: max(filterWidgets.length + (activeData <= 1 ? 1 : 0), 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (filterWidgets.isNotEmpty) {
|
||||
if ((index == 0 && activeData == 1) || (index == 0 && activeData == 0)) {
|
||||
if ((index == 0 && activeData == 1) ||
|
||||
(index == 0 && activeData == 0)) {
|
||||
int value1 = 0;
|
||||
int value2 = 0;
|
||||
String title1 = "";
|
||||
@ -292,18 +325,26 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
String suffix = "";
|
||||
|
||||
if (activeData == AbsenceFilter.absences.index) {
|
||||
value1 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.excused).length;
|
||||
value2 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.unexcused).length;
|
||||
value1 = absenceProvider.absences
|
||||
.where((e) =>
|
||||
e.delay == 0 && e.state == Justification.excused)
|
||||
.length;
|
||||
value2 = absenceProvider.absences
|
||||
.where((e) =>
|
||||
e.delay == 0 && e.state == Justification.unexcused)
|
||||
.length;
|
||||
title1 = "stat_1".i18n;
|
||||
title2 = "stat_2".i18n;
|
||||
suffix = " " + "hr".i18n;
|
||||
} else if (activeData == AbsenceFilter.delays.index) {
|
||||
value1 = absenceProvider.absences
|
||||
.where((e) => e.delay != 0 && e.state == Justification.excused)
|
||||
.where((e) =>
|
||||
e.delay != 0 && e.state == Justification.excused)
|
||||
.map((e) => e.delay)
|
||||
.fold(0, (a, b) => a + b);
|
||||
value2 = absenceProvider.absences
|
||||
.where((e) => e.delay != 0 && e.state == Justification.unexcused)
|
||||
.where((e) =>
|
||||
e.delay != 0 && e.state == Justification.unexcused)
|
||||
.map((e) => e.delay)
|
||||
.fold(0, (a, b) => a + b);
|
||||
title1 = "stat_3".i18n;
|
||||
@ -312,7 +353,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0, left: 24.0, right: 24.0),
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 24.0, left: 24.0, right: 24.0),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: StatisticsTile(
|
||||
@ -348,7 +390,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 6.0),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24.0, vertical: 6.0),
|
||||
child: filterWidgets[index - (activeData <= 1 ? 1 : 0)],
|
||||
);
|
||||
} else {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_desktop_ui/common/filter_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/filter_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animated_list_plus/animated_list_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -16,7 +16,8 @@ class HomePage extends StatefulWidget {
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
|
||||
class _HomePageState extends State<HomePage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late UserProvider user;
|
||||
late SettingsProvider settings;
|
||||
|
||||
@ -41,11 +42,15 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
||||
user = Provider.of<UserProvider>(context, listen: false);
|
||||
|
||||
DateTime now = DateTime.now();
|
||||
if (now.isBefore(DateTime(now.year, DateTime.august, 31)) && now.isAfter(DateTime(now.year, DateTime.june, 14))) {
|
||||
if (now.isBefore(DateTime(now.year, DateTime.august, 31)) &&
|
||||
now.isAfter(DateTime(now.year, DateTime.june, 14))) {
|
||||
greeting = "goodrest";
|
||||
} else if (now.month == user.student?.birth.month && now.day == user.student?.birth.day) {
|
||||
} else if (now.month == user.student?.birth.month &&
|
||||
now.day == user.student?.birth.day) {
|
||||
greeting = "happybirthday";
|
||||
} else if (now.month == DateTime.december && now.day >= 24 && now.day <= 26) {
|
||||
} else if (now.month == DateTime.december &&
|
||||
now.day >= 24 &&
|
||||
now.day <= 26) {
|
||||
greeting = "merryxmas";
|
||||
} else if (now.month == DateTime.january && now.day == 1) {
|
||||
greeting = "happynewyear";
|
||||
@ -81,7 +86,8 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
||||
children: [
|
||||
// Greeting
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32.0, top: 24.0, bottom: 12.0),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32.0, top: 24.0, bottom: 12.0),
|
||||
child: Text(
|
||||
greeting.i18n.fill([firstName]),
|
||||
overflow: TextOverflow.fade,
|
||||
@ -107,8 +113,10 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
||||
int selectedPage = _pageController.page!.round();
|
||||
|
||||
if (i == selectedPage) return;
|
||||
if (_pageController.page?.roundToDouble() != _pageController.page) {
|
||||
_pageController.animateToPage(i, curve: Curves.easeIn, duration: kTabScrollDuration);
|
||||
if (_pageController.page?.roundToDouble() !=
|
||||
_pageController.page) {
|
||||
_pageController.animateToPage(i,
|
||||
curve: Curves.easeIn, duration: kTabScrollDuration);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,27 +140,34 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
||||
(BuildContext context, int index) {
|
||||
return FutureBuilder<List<DateWidget>>(
|
||||
key: ValueKey<String>(listOrder[index]),
|
||||
future: getFilterWidgets(homeFilters[index], context: context),
|
||||
builder: (context, dateWidgets) => dateWidgets.data != null
|
||||
future: getFilterWidgets(homeFilters[index],
|
||||
context: context),
|
||||
builder: (context, dateWidgets) => dateWidgets.data !=
|
||||
null
|
||||
? ImplicitlyAnimatedList<Widget>(
|
||||
items: sortDateWidgets(context, dateWidgets: dateWidgets.data!),
|
||||
items: sortDateWidgets(context,
|
||||
dateWidgets: dateWidgets.data!),
|
||||
itemBuilder: filterItemBuilder,
|
||||
spawnIsolate: false,
|
||||
areItemsTheSame: (a, b) => a.key == b.key,
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
physics: const BouncingScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics()),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0),
|
||||
)
|
||||
: Container(),
|
||||
);
|
||||
},
|
||||
childCount: 4,
|
||||
findChildIndexCallback: (Key key) {
|
||||
final ValueKey<String> valueKey = key as ValueKey<String>;
|
||||
final ValueKey<String> valueKey =
|
||||
key as ValueKey<String>;
|
||||
final String data = valueKey.value;
|
||||
return listOrder.indexOf(data);
|
||||
},
|
||||
),
|
||||
physics: const PageScrollPhysics().applyTo(const BouncingScrollPhysics()),
|
||||
physics: const PageScrollPhysics()
|
||||
.applyTo(const BouncingScrollPhysics()),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -23,12 +23,14 @@ import 'timetable_page.i18n.dart';
|
||||
// todo: "fix" overflow (priority: -1)
|
||||
|
||||
class TimetablePage extends StatefulWidget {
|
||||
const TimetablePage({Key? key, this.initialDay, this.initialWeek}) : super(key: key);
|
||||
const TimetablePage({Key? key, this.initialDay, this.initialWeek})
|
||||
: super(key: key);
|
||||
|
||||
final DateTime? initialDay;
|
||||
final Week? initialWeek;
|
||||
|
||||
static void jump(BuildContext context, {Week? week, DateTime? day, Lesson? lesson}) {
|
||||
static void jump(BuildContext context,
|
||||
{Week? week, DateTime? day, Lesson? lesson}) {
|
||||
// Go to timetable page with arguments
|
||||
// NavigationScreen.of(context)?.customRoute(navigationPageRoute((context) => TimetablePage(
|
||||
// initialDay: lesson?.date ?? day,
|
||||
@ -49,7 +51,8 @@ class TimetablePage extends StatefulWidget {
|
||||
_TimetablePageState createState() => _TimetablePageState();
|
||||
}
|
||||
|
||||
class _TimetablePageState extends State<TimetablePage> with TickerProviderStateMixin {
|
||||
class _TimetablePageState extends State<TimetablePage>
|
||||
with TickerProviderStateMixin {
|
||||
late UserProvider user;
|
||||
late TimetableProvider timetableProvider;
|
||||
late UpdateProvider updateProvider;
|
||||
@ -60,7 +63,9 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
|
||||
int _getDayIndex(DateTime date) {
|
||||
int index = 0;
|
||||
if (_controller.days == null || (_controller.days?.isEmpty ?? true)) return index;
|
||||
if (_controller.days == null || (_controller.days?.isEmpty ?? true)) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// find the first day with upcoming lessons
|
||||
index = _controller.days!.indexWhere((day) => day.last.end.isAfter(date));
|
||||
@ -94,11 +99,14 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
_tabController = TabController(
|
||||
length: _controller.days!.length,
|
||||
vsync: this,
|
||||
initialIndex: min(_tabController.index, max(_controller.days!.length - 1, 0)),
|
||||
initialIndex:
|
||||
min(_tabController.index, max(_controller.days!.length - 1, 0)),
|
||||
);
|
||||
|
||||
if (initial || _controller.previousWeekId != _controller.currentWeekId) {
|
||||
_tabController.animateTo(_getDayIndex(widget.initialDay ?? DateTime.now()));
|
||||
if (initial ||
|
||||
_controller.previousWeekId != _controller.currentWeekId) {
|
||||
_tabController
|
||||
.animateTo(_getDayIndex(widget.initialDay ?? DateTime.now()));
|
||||
}
|
||||
initial = false;
|
||||
|
||||
@ -111,7 +119,8 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
if (widget.initialWeek != null) {
|
||||
_controller.jump(widget.initialWeek!, context: context, initial: true);
|
||||
} else {
|
||||
_controller.jump(_controller.currentWeek, context: context, initial: true, skip: true);
|
||||
_controller.jump(_controller.currentWeek,
|
||||
context: context, initial: true, skip: true);
|
||||
}
|
||||
}
|
||||
// Listen for user changes
|
||||
@ -131,7 +140,8 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
// Sometimes when changing weeks really fast,
|
||||
// controller.days might be null or won't include index
|
||||
try {
|
||||
return DateFormat("EEEE", I18n.of(context).locale.languageCode).format(_controller.days![index].first.date);
|
||||
return DateFormat("EEEE", I18n.of(context).locale.languageCode)
|
||||
.format(_controller.days![index].first.date);
|
||||
} catch (e) {
|
||||
return "timetable".i18n;
|
||||
}
|
||||
@ -181,9 +191,14 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
children: [
|
||||
// Day Title
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24.0, right: 28.0, top: 18.0, bottom: 8.0),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24.0,
|
||||
right: 28.0,
|
||||
top: 18.0,
|
||||
bottom: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
dayTitle(tab).capital(),
|
||||
@ -193,9 +208,13 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${_controller.days![tab].first.date.day}".padLeft(2, '0') + ".",
|
||||
"${_controller.days![tab].first.date.day}"
|
||||
.padLeft(2, '0') +
|
||||
".",
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.5),
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(.5),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@ -208,35 +227,59 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: _controller.days![tab].length + 2,
|
||||
itemCount:
|
||||
_controller.days![tab].length + 2,
|
||||
itemBuilder: (context, index) {
|
||||
if (_controller.days == null) return Container();
|
||||
if (_controller.days == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
// Header
|
||||
if (index == 0) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 8.0, left: 24.0, right: 24.0),
|
||||
child: PanelHeader(padding: EdgeInsets.only(top: 12.0)),
|
||||
padding: EdgeInsets.only(
|
||||
top: 8.0,
|
||||
left: 24.0,
|
||||
right: 24.0),
|
||||
child: PanelHeader(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0)),
|
||||
);
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (index == _controller.days![tab].length + 1) {
|
||||
if (index ==
|
||||
_controller.days![tab].length +
|
||||
1) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8.0, left: 24.0, right: 24.0),
|
||||
child: PanelFooter(padding: EdgeInsets.only(top: 12.0)),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 8.0,
|
||||
left: 24.0,
|
||||
right: 24.0),
|
||||
child: PanelFooter(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0)),
|
||||
);
|
||||
}
|
||||
|
||||
// Body
|
||||
final Lesson lesson = _controller.days![tab][index - 1];
|
||||
final bool swapDescDay = _controller.days![tab].map((l) => l.swapDesc ? 1 : 0).reduce((a, b) => a + b) >=
|
||||
_controller.days![tab].length * .5;
|
||||
final Lesson lesson =
|
||||
_controller.days![tab][index - 1];
|
||||
final bool swapDescDay = _controller
|
||||
.days![tab]
|
||||
.map(
|
||||
(l) => l.swapDesc ? 1 : 0)
|
||||
.reduce((a, b) => a + b) >=
|
||||
_controller.days![tab].length *
|
||||
.5;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0),
|
||||
child: PanelBody(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 10.0),
|
||||
child: LessonViewable(
|
||||
lesson,
|
||||
swapDesc: swapDescDay,
|
||||
@ -264,7 +307,8 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -285,7 +329,10 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
onTap: () => setState(() {
|
||||
_controller.current();
|
||||
if (mounted) {
|
||||
_controller.jump(_controller.currentWeek, context: context, loader: _controller.currentWeekId != _controller.previousWeekId);
|
||||
_controller.jump(_controller.currentWeek,
|
||||
context: context,
|
||||
loader: _controller.currentWeekId !=
|
||||
_controller.previousWeekId);
|
||||
}
|
||||
}),
|
||||
child: Padding(
|
||||
@ -295,12 +342,22 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
||||
"week".i18n +
|
||||
" (" +
|
||||
// Week start
|
||||
DateFormat((_controller.currentWeek.start.year != DateTime.now().year ? "yy. " : "") + "MMM d.",
|
||||
DateFormat(
|
||||
(_controller.currentWeek.start.year !=
|
||||
DateTime.now().year
|
||||
? "yy. "
|
||||
: "") +
|
||||
"MMM d.",
|
||||
I18n.of(context).locale.languageCode)
|
||||
.format(_controller.currentWeek.start) +
|
||||
" - " +
|
||||
// Week end
|
||||
DateFormat((_controller.currentWeek.start.year != DateTime.now().year ? "yy. " : "") + "MMM d.",
|
||||
DateFormat(
|
||||
(_controller.currentWeek.start.year !=
|
||||
DateTime.now().year
|
||||
? "yy. "
|
||||
: "") +
|
||||
"MMM d.",
|
||||
I18n.of(context).locale.languageCode)
|
||||
.format(_controller.currentWeek.end) +
|
||||
")",
|
||||
|
@ -16,13 +16,15 @@ import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||
class NavigationScreen extends StatefulWidget {
|
||||
const NavigationScreen({Key? key}) : super(key: key);
|
||||
|
||||
static NavigationScreenState? of(BuildContext context) => context.findAncestorStateOfType<NavigationScreenState>();
|
||||
static NavigationScreenState? of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<NavigationScreenState>();
|
||||
|
||||
@override
|
||||
State<NavigationScreen> createState() => NavigationScreenState();
|
||||
}
|
||||
|
||||
class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingObserver {
|
||||
class NavigationScreenState extends State<NavigationScreen>
|
||||
with WidgetsBindingObserver {
|
||||
final _navigatorState = GlobalKey<NavigatorState>();
|
||||
late NavigationRoute selected;
|
||||
late SettingsProvider settings;
|
||||
@ -40,7 +42,8 @@ class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingO
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// set client User-Agent
|
||||
Provider.of<KretaClient>(context, listen: false).userAgent = settings.config.userAgent;
|
||||
Provider.of<KretaClient>(context, listen: false).userAgent =
|
||||
settings.config.userAgent;
|
||||
|
||||
// Get news
|
||||
newsProvider = Provider.of<NewsProvider>(context, listen: false);
|
||||
@ -54,7 +57,11 @@ class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingO
|
||||
await Window.initialize();
|
||||
} catch (_) {}
|
||||
// Transparent sidebar
|
||||
await Window.setEffect(effect: WindowEffect.acrylic, color: Platform.isMacOS ? Colors.transparent : const Color.fromARGB(27, 27, 27, 27));
|
||||
await Window.setEffect(
|
||||
effect: WindowEffect.acrylic,
|
||||
color: Platform.isMacOS
|
||||
? Colors.transparent
|
||||
: const Color.fromARGB(27, 27, 27, 27));
|
||||
|
||||
// todo: do for windows
|
||||
if (Platform.isMacOS) {
|
||||
@ -75,8 +82,12 @@ class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingO
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
if (settings.theme == ThemeMode.system) {
|
||||
Brightness? brightness = WidgetsBinding.instance.window.platformBrightness;
|
||||
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark);
|
||||
// ignore: deprecated_member_use
|
||||
Brightness? brightness =
|
||||
// ignore: deprecated_member_use
|
||||
WidgetsBinding.instance.window.platformBrightness;
|
||||
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(
|
||||
brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark);
|
||||
}
|
||||
super.didChangePlatformBrightness();
|
||||
}
|
||||
@ -103,8 +114,12 @@ class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingO
|
||||
if (_navigatorState.currentState != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(.5),
|
||||
border: Border(right: BorderSide(color: AppColors.of(context).shadow.withOpacity(.7), width: 1.0)),
|
||||
color:
|
||||
Theme.of(context).scaffoldBackgroundColor.withOpacity(.5),
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: AppColors.of(context).shadow.withOpacity(.7),
|
||||
width: 1.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: topInset),
|
||||
@ -125,7 +140,8 @@ class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingO
|
||||
child: Navigator(
|
||||
key: _navigatorState,
|
||||
initialRoute: selected.name,
|
||||
onGenerateRoute: (settings) => navigationRouteHandler(settings),
|
||||
onGenerateRoute: (settings) =>
|
||||
navigationRouteHandler(settings),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -6,6 +6,7 @@ import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_desktop_ui/common/panel_button.dart';
|
||||
import 'package:filcnaplo_desktop_ui/common/profile_image.dart';
|
||||
import 'package:filcnaplo_desktop_ui/screens/navigation/sidebar.i18n.dart';
|
||||
import 'package:filcnaplo_desktop_ui/screens/navigation/sidebar_action.dart';
|
||||
import 'package:filcnaplo_desktop_ui/screens/settings/settings_screen.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/settings/accounts/account_tile.dart';
|
||||
@ -25,7 +26,12 @@ import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
|
||||
class Sidebar extends StatefulWidget {
|
||||
const Sidebar({Key? key, required this.navigator, required this.onRouteChange, this.selected = "home"}) : super(key: key);
|
||||
const Sidebar(
|
||||
{Key? key,
|
||||
required this.navigator,
|
||||
required this.onRouteChange,
|
||||
this.selected = "home"})
|
||||
: super(key: key);
|
||||
|
||||
final NavigatorState navigator;
|
||||
final String selected;
|
||||
@ -71,12 +77,12 @@ class _SidebarState extends State<Sidebar> {
|
||||
if (!settings.presentationMode) {
|
||||
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
||||
} else {
|
||||
firstName = "Béla";
|
||||
firstName = "János";
|
||||
}
|
||||
|
||||
List<Widget> pageWidgets = [
|
||||
SidebarAction(
|
||||
title: const Text("Home"),
|
||||
title: Text("Home".i18n),
|
||||
icon: const Icon(FilcIcons.home),
|
||||
selected: widget.selected == "home",
|
||||
onTap: () {
|
||||
@ -87,7 +93,7 @@ class _SidebarState extends State<Sidebar> {
|
||||
},
|
||||
),
|
||||
SidebarAction(
|
||||
title: const Text("Grades"),
|
||||
title: Text("Grades".i18n),
|
||||
icon: const Icon(FeatherIcons.bookmark),
|
||||
selected: widget.selected == "grades",
|
||||
onTap: () {
|
||||
@ -98,7 +104,7 @@ class _SidebarState extends State<Sidebar> {
|
||||
},
|
||||
),
|
||||
SidebarAction(
|
||||
title: const Text("Timetable"),
|
||||
title: Text("Timetable".i18n),
|
||||
icon: const Icon(FeatherIcons.calendar),
|
||||
selected: widget.selected == "timetable",
|
||||
onTap: () {
|
||||
@ -109,7 +115,7 @@ class _SidebarState extends State<Sidebar> {
|
||||
},
|
||||
),
|
||||
SidebarAction(
|
||||
title: const Text("Messages"),
|
||||
title: Text("Messages".i18n),
|
||||
icon: const Icon(FeatherIcons.messageSquare),
|
||||
selected: widget.selected == "messages",
|
||||
onTap: () {
|
||||
@ -120,7 +126,7 @@ class _SidebarState extends State<Sidebar> {
|
||||
},
|
||||
),
|
||||
SidebarAction(
|
||||
title: const Text("Absences"),
|
||||
title: Text("Absences".i18n),
|
||||
icon: const Icon(FeatherIcons.clock),
|
||||
selected: widget.selected == "absences",
|
||||
onTap: () {
|
||||
@ -134,12 +140,15 @@ class _SidebarState extends State<Sidebar> {
|
||||
|
||||
List<Widget> bottomActions = [
|
||||
SidebarAction(
|
||||
title: const Text("Settings"),
|
||||
title: Text("Settings".i18n),
|
||||
selected: true,
|
||||
icon: const Icon(FeatherIcons.settings),
|
||||
onTap: () {
|
||||
if (topNav != "settings") {
|
||||
widget.navigator.push(CupertinoPageRoute(builder: (context) => const SettingsScreen())).then((value) => topNav = "");
|
||||
widget.navigator
|
||||
.push(CupertinoPageRoute(
|
||||
builder: (context) => const SettingsScreen()))
|
||||
.then((value) => topNav = "");
|
||||
topNav = "settings";
|
||||
}
|
||||
},
|
||||
@ -159,7 +168,7 @@ class _SidebarState extends State<Sidebar> {
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed("login_back");
|
||||
},
|
||||
title: const Text("Add User"),
|
||||
title: Text("adduser".i18n),
|
||||
leading: const Icon(FeatherIcons.userPlus),
|
||||
),
|
||||
PanelButton(
|
||||
@ -169,17 +178,20 @@ class _SidebarState extends State<Sidebar> {
|
||||
|
||||
// Delete User
|
||||
user.removeUser(userId);
|
||||
await Provider.of<DatabaseProvider>(context, listen: false).store.removeUser(userId);
|
||||
await Provider.of<DatabaseProvider>(context, listen: false)
|
||||
.store
|
||||
.removeUser(userId);
|
||||
|
||||
// If no other Users left, go back to LoginScreen
|
||||
if (user.getUsers().isNotEmpty) {
|
||||
user.setUser(user.getUsers().first.id);
|
||||
restore().then((_) => user.setUser(user.getUsers().first.id));
|
||||
} else {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil("login", (_) => false);
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil("login", (_) => false);
|
||||
}
|
||||
},
|
||||
title: const Text("Log Out"),
|
||||
title: Text("logout".i18n),
|
||||
leading: Icon(FeatherIcons.logOut, color: AppColors.of(context).red),
|
||||
),
|
||||
];
|
||||
@ -190,16 +202,37 @@ class _SidebarState extends State<Sidebar> {
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 18.0, top: 18.0, bottom: 24.0, right: 8.0),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
top: 18.0,
|
||||
bottom: 24.0,
|
||||
right: 12.0,
|
||||
),
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
expandAccount = !expandAccount;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
padding: const EdgeInsets.only(
|
||||
right: 12.0,
|
||||
left: 5.0,
|
||||
top: 5.0,
|
||||
bottom: 5.0,
|
||||
),
|
||||
child: ProfileImage(
|
||||
name: firstName,
|
||||
radius: 18.0,
|
||||
backgroundColor:
|
||||
!settings.presentationMode ? ColorUtils.stringToColor(user.name ?? "?") : Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary, //!settings.presentationMode
|
||||
// ? ColorUtils.stringToColor(user.name ?? "?")
|
||||
// : Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@ -212,7 +245,8 @@ class _SidebarState extends State<Sidebar> {
|
||||
),
|
||||
),
|
||||
PageTransitionSwitcher(
|
||||
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
|
||||
transitionBuilder:
|
||||
(child, primaryAnimation, secondaryAnimation) {
|
||||
return FadeThroughTransition(
|
||||
fillColor: Colors.transparent,
|
||||
animation: primaryAnimation,
|
||||
@ -222,19 +256,25 @@ class _SidebarState extends State<Sidebar> {
|
||||
},
|
||||
child: IconButton(
|
||||
key: Key(expandAccount ? "accounts" : "pages"),
|
||||
icon: Icon(expandAccount ? FeatherIcons.chevronDown : FeatherIcons.chevronRight),
|
||||
icon: Icon(expandAccount
|
||||
? FeatherIcons.chevronDown
|
||||
: FeatherIcons.chevronRight),
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: 20.0,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
expandAccount = !expandAccount;
|
||||
});
|
||||
},
|
||||
splashColor: const Color(0x00000000),
|
||||
focusColor: const Color(0x00000000),
|
||||
hoverColor: const Color(0x00000000),
|
||||
highlightColor: const Color(0x00000000),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Pages
|
||||
Expanded(
|
||||
@ -282,15 +322,19 @@ class _SidebarState extends State<Sidebar> {
|
||||
if (!settings.presentationMode) {
|
||||
_firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0];
|
||||
} else {
|
||||
_firstName = "Béla";
|
||||
_firstName = "János";
|
||||
}
|
||||
|
||||
accountTiles.add(AccountTile(
|
||||
name: Text(!settings.presentationMode ? account.name : "Béla", style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
username: Text(!settings.presentationMode ? account.username : "72469696969"),
|
||||
name: Text(!settings.presentationMode ? account.name : "János",
|
||||
style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
username:
|
||||
Text(!settings.presentationMode ? account.username : "72469696969"),
|
||||
profileImage: ProfileImage(
|
||||
name: _firstName,
|
||||
backgroundColor: !settings.presentationMode ? ColorUtils.stringToColor(account.name) : Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: !settings.presentationMode
|
||||
? ColorUtils.stringToColor(account.name)
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
role: account.role,
|
||||
),
|
||||
onTap: () {
|
||||
|
@ -0,0 +1,42 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Home": "Home",
|
||||
"Grades": "Grades",
|
||||
"Timetable": "Timetable",
|
||||
"Messages": "Messages",
|
||||
"Absences": "Absences",
|
||||
"Settings": "Settings",
|
||||
"adduser": "Add User",
|
||||
"logout": "Log Out",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Home": "Kezdőlap",
|
||||
"Grades": "Jegyek",
|
||||
"Timetable": "Órarend",
|
||||
"Messages": "Üzenetek",
|
||||
"Absences": "Hiányzások",
|
||||
"Settings": "Beállítások",
|
||||
"adduser": "Fiók hozzáadása",
|
||||
"logout": "Kilépés",
|
||||
},
|
||||
"de_de": {
|
||||
"Home": "Zuhause",
|
||||
"Grades": "Noten",
|
||||
"Timetable": "Zeitplan",
|
||||
"Messages": "Mitteilungen",
|
||||
"Absences": "Fehlen",
|
||||
"Settings": "Einstellungen",
|
||||
"adduser": "Benutzer hinzufügen",
|
||||
"logout": "Abmelden",
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
import 'package:filcnaplo/api/providers/update_provider.dart';
|
||||
import 'package:filcnaplo/theme/colors/accent.dart';
|
||||
import 'package:filcnaplo/theme/observer.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
|
||||
@ -35,6 +33,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as tabs;
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'settings_screen.i18n.dart';
|
||||
@ -86,11 +85,11 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
if (!settings.presentationMode) {
|
||||
_firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0];
|
||||
} else {
|
||||
_firstName = "Béla";
|
||||
_firstName = "János";
|
||||
}
|
||||
|
||||
accountTiles.add(AccountTile(
|
||||
name: Text(!settings.presentationMode ? account.name : "Béla",
|
||||
name: Text(!settings.presentationMode ? account.name : "János",
|
||||
style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
username:
|
||||
Text(!settings.presentationMode ? account.username : "72469696969"),
|
||||
@ -164,7 +163,7 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
if (!settings.presentationMode) {
|
||||
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
||||
} else {
|
||||
firstName = "Béla";
|
||||
firstName = "János";
|
||||
}
|
||||
|
||||
String startPageTitle =
|
||||
@ -206,14 +205,21 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
animation: _hideContainersController,
|
||||
builder: (context, child) => Opacity(
|
||||
opacity: 1 - _hideContainersController.value,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StaggeredGrid.extent(
|
||||
// direction: Axis.horizontal,
|
||||
// crossAxisCount: 3,
|
||||
maxCrossAxisExtent: 600,
|
||||
children: [
|
||||
const SizedBox(height: 32.0),
|
||||
|
||||
// Updates
|
||||
if (updateProvider.available)
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -225,8 +231,10 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
updateProvider.releases.first.tag,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -246,7 +254,9 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
|
||||
// General Settings
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -277,7 +287,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
setState(() {});
|
||||
},
|
||||
title: Text("rounding".i18n),
|
||||
leading: const Icon(FeatherIcons.gitCommit),
|
||||
leading:
|
||||
const Icon(FeatherIcons.gitCommit),
|
||||
trailing: Text((settings.rounding / 10)
|
||||
.toStringAsFixed(1)),
|
||||
),
|
||||
@ -291,7 +302,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
trailing: Text(vibrateTitle),
|
||||
),
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0),
|
||||
padding:
|
||||
const EdgeInsets.only(left: 14.0),
|
||||
onPressed: () {
|
||||
SettingsHelper.bellDelay(context);
|
||||
setState(() {});
|
||||
@ -314,20 +326,24 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
.withOpacity(.25)),
|
||||
trailingDivider: true,
|
||||
trailing: Switch(
|
||||
onChanged: (v) =>
|
||||
settings.update(bellDelayEnabled: v),
|
||||
onChanged: (v) => settings.update(
|
||||
bellDelayEnabled: v),
|
||||
value: settings.bellDelayEnabled,
|
||||
activeColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (kDebugMode)
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -335,12 +351,15 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
child: Column(
|
||||
children: [
|
||||
PanelButton(
|
||||
title: const Text("Subject Icon Gallery"),
|
||||
title:
|
||||
const Text("Subject Icon Gallery"),
|
||||
leading: const Icon(CupertinoIcons
|
||||
.rectangle_3_offgrid_fill),
|
||||
trailing: const Icon(Icons.arrow_forward),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
Navigator.of(context,
|
||||
rootNavigator: true)
|
||||
.push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
@ -352,10 +371,13 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Secret Settings
|
||||
if (__ss)
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -378,28 +400,35 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
if (v) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => WillPopScope(
|
||||
builder: (context) =>
|
||||
WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
shape:
|
||||
RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
BorderRadius
|
||||
.circular(
|
||||
12.0)),
|
||||
title: Text("attention".i18n),
|
||||
title:
|
||||
Text("attention".i18n),
|
||||
content: Text(
|
||||
"goodstudent_disclaimer"
|
||||
.i18n),
|
||||
actions: [
|
||||
ActionButton(
|
||||
label: "understand".i18n,
|
||||
label:
|
||||
"understand".i18n,
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
settings.update(
|
||||
goodStudent: v);
|
||||
Provider.of<GradeProvider>(
|
||||
context,
|
||||
listen: false)
|
||||
listen:
|
||||
false)
|
||||
.fetch();
|
||||
})
|
||||
],
|
||||
@ -408,7 +437,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
);
|
||||
} else {
|
||||
settings.update(goodStudent: v);
|
||||
Provider.of<GradeProvider>(context,
|
||||
Provider.of<GradeProvider>(
|
||||
context,
|
||||
listen: false)
|
||||
.fetch();
|
||||
}
|
||||
@ -432,8 +462,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
title: const Text("Presentation Mode",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500)),
|
||||
onChanged: (v) =>
|
||||
settings.update(presentationMode: v),
|
||||
onChanged: (v) => settings.update(
|
||||
presentationMode: v),
|
||||
value: settings.presentationMode,
|
||||
activeColor: Theme.of(context)
|
||||
.colorScheme
|
||||
@ -444,9 +474,12 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Theme Settings
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -464,7 +497,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
PanelButton(
|
||||
onPressed: () async {
|
||||
await _hideContainersController.forward();
|
||||
await _hideContainersController
|
||||
.forward();
|
||||
SettingsHelper.accentColor(context);
|
||||
setState(() {});
|
||||
_hideContainersController.reset();
|
||||
@ -494,8 +528,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
children: List.generate(
|
||||
5,
|
||||
(i) => Container(
|
||||
margin:
|
||||
const EdgeInsets.only(left: 2.0),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 2.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
@ -547,8 +581,9 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
onChanged: (v) =>
|
||||
settings.update(graphClassAvg: v),
|
||||
value: settings.graphClassAvg,
|
||||
activeColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
),
|
||||
const PremiumIconPackSelector(),
|
||||
@ -556,9 +591,12 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Notifications
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -569,7 +607,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 12.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
borderRadius:
|
||||
BorderRadius.circular(12.0)),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
@ -591,7 +630,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(settings.newsEnabled
|
||||
.withOpacity(
|
||||
settings.newsEnabled
|
||||
? 1.0
|
||||
: .5),
|
||||
),
|
||||
@ -608,9 +648,12 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Extras
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -657,16 +700,20 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
onChanged: (v) =>
|
||||
settings.update(gradeOpeningFun: v),
|
||||
value: settings.gradeOpeningFun,
|
||||
activeColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// About
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -676,7 +723,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
leading: const Icon(FeatherIcons.atSign),
|
||||
title: const Text("Discord"),
|
||||
onPressed: () => launchUrl(
|
||||
Uri.parse("https://filcnaplo.hu/discord"),
|
||||
Uri.parse(
|
||||
"https://filcnaplo.hu/discord"),
|
||||
mode: LaunchMode.externalApplication),
|
||||
),
|
||||
PanelButton(
|
||||
@ -765,9 +813,10 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
String newId;
|
||||
if (v == false) {
|
||||
newId = "none";
|
||||
} else if (settings.xFilcId == "none") {
|
||||
newId =
|
||||
SettingsProvider.defaultSettings()
|
||||
} else if (settings.xFilcId ==
|
||||
"none") {
|
||||
newId = SettingsProvider
|
||||
.defaultSettings()
|
||||
.xFilcId;
|
||||
} else {
|
||||
newId = settings.xFilcId;
|
||||
@ -775,16 +824,20 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
settings.update(xFilcId: newId);
|
||||
},
|
||||
value: settings.xFilcId != "none",
|
||||
activeColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (settings.developerMode)
|
||||
Padding(
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
@ -802,8 +855,8 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
title: const Text("Developer Mode",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500)),
|
||||
onChanged: (v) =>
|
||||
settings.update(developerMode: false),
|
||||
onChanged: (v) => settings.update(
|
||||
developerMode: false),
|
||||
value: settings.developerMode,
|
||||
activeColor: Theme.of(context)
|
||||
.colorScheme
|
||||
@ -820,28 +873,36 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
listen: false)
|
||||
.accessToken!)),
|
||||
),
|
||||
if (Provider.of<PremiumProvider>(context,
|
||||
listen: false)
|
||||
.hasPremium)
|
||||
PanelButton(
|
||||
leading: const Icon(FeatherIcons.key),
|
||||
title: const Text("Remove Premium"),
|
||||
onPressed: () {
|
||||
Provider.of<PremiumProvider>(context,
|
||||
listen: false)
|
||||
.activate(removePremium: true);
|
||||
settings.update(
|
||||
accentColor: AccentColor.filc,
|
||||
store: true);
|
||||
Provider.of<ThemeModeObserver>(context,
|
||||
listen: false)
|
||||
.changeTheme(settings.theme);
|
||||
},
|
||||
),
|
||||
// if (Provider.of<PremiumProvider>(context,
|
||||
// listen: false)
|
||||
// .hasPremium)
|
||||
// PanelButton(
|
||||
// leading: const Icon(FeatherIcons.key),
|
||||
// title: const Text("Remove Premium"),
|
||||
// onPressed: () {
|
||||
// Provider.of<PremiumProvider>(
|
||||
// context,
|
||||
// listen: false)
|
||||
// .activate(removePremium: true);
|
||||
// settings.update(
|
||||
// accentColor: AccentColor.filc,
|
||||
// store: true);
|
||||
// Provider.of<ThemeModeObserver>(
|
||||
// context,
|
||||
// listen: false)
|
||||
// .changeTheme(settings.theme);
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: Center(
|
||||
@ -880,7 +941,6 @@ class _SettingsScreenState extends State<SettingsScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -25,6 +25,7 @@ dependencies:
|
||||
auto_size_text: ^3.0.0
|
||||
flutter_acrylic: ^1.1.3
|
||||
elegant_notification: ^1.6.1
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^1.0.0
|
||||
|
@ -7,39 +7,69 @@ class KretaAPI {
|
||||
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 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;
|
||||
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;
|
||||
(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);
|
||||
(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;
|
||||
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 {
|
||||
@ -58,25 +88,32 @@ class KretaApiEndpoints {
|
||||
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 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";
|
||||
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 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 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";
|
||||
// profile management
|
||||
static const editProfile = "/api/profilapi/saveprofildata";
|
||||
}
|
||||
|
@ -66,8 +66,12 @@ class KretaClient {
|
||||
|
||||
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("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);
|
||||
@ -94,7 +98,8 @@ class KretaClient {
|
||||
return res.body;
|
||||
}
|
||||
} on http.ClientException catch (error) {
|
||||
print("ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}");
|
||||
print(
|
||||
"ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}");
|
||||
} catch (error) {
|
||||
print("ERROR: KretaClient.getAPI ($url) ${error.runtimeType}: $error");
|
||||
}
|
||||
@ -120,9 +125,15 @@ class KretaClient {
|
||||
|
||||
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";
|
||||
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);
|
||||
@ -142,9 +153,10 @@ class KretaClient {
|
||||
return res.body;
|
||||
}
|
||||
} on http.ClientException catch (error) {
|
||||
print("ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}");
|
||||
print(
|
||||
"ERROR: KretaClient.postAPI ($url) ClientException: ${error.message}");
|
||||
} catch (error) {
|
||||
print("ERROR: KretaClient.getAPI ($url) ${error.runtimeType}: $error");
|
||||
print("ERROR: KretaClient.postAPI ($url) ${error.runtimeType}: $error");
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +169,8 @@ class KretaClient {
|
||||
};
|
||||
|
||||
String nonceStr = await getAPI(KretaAPI.nonce, json: false);
|
||||
Nonce nonce = getNonce(nonceStr, loginUser.username, loginUser.instituteCode);
|
||||
Nonce nonce =
|
||||
getNonce(nonceStr, loginUser.username, loginUser.instituteCode);
|
||||
headers.addAll(nonce.header());
|
||||
|
||||
if (_settings.presentationMode) {
|
||||
@ -169,24 +182,34 @@ class KretaClient {
|
||||
Map? loginRes = await postAPI(KretaAPI.login,
|
||||
headers: headers,
|
||||
body: User.loginBody(
|
||||
username: loginUser.username.replaceAll(' ', '') + ' ',
|
||||
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"];
|
||||
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;
|
||||
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));
|
||||
headers: headers,
|
||||
body: User.refreshBody(
|
||||
refreshToken: refreshToken!,
|
||||
instituteCode: loginUser.instituteCode));
|
||||
if (refreshRes != null) {
|
||||
if (refreshRes.containsKey("id_token")) idToken = refreshRes["id_token"];
|
||||
if (refreshRes.containsKey("id_token")) {
|
||||
idToken = refreshRes["id_token"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ProfileController extends ChangeNotifier {
|
||||
//todo: profile controller (pw change, etc)
|
||||
}
|
@ -24,7 +24,10 @@ class TimetableController extends ChangeNotifier {
|
||||
current();
|
||||
}
|
||||
|
||||
static int getWeekId(Week week) => (week.start.difference(getSchoolYearStart()).inDays / DateTime.daysPerWeek).ceil();
|
||||
static int getWeekId(Week week) =>
|
||||
(week.start.difference(getSchoolYearStart()).inDays /
|
||||
DateTime.daysPerWeek)
|
||||
.ceil();
|
||||
|
||||
static DateTime getSchoolYearStart() {
|
||||
DateTime now = DateTime.now();
|
||||
@ -48,8 +51,10 @@ class TimetableController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// 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);
|
||||
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);
|
||||
@ -60,7 +65,11 @@ class TimetableController extends ChangeNotifier {
|
||||
_setWeek(Week.fromId(id));
|
||||
}
|
||||
|
||||
Future<void> jump(Week week, {required BuildContext context, bool initial = false, bool skip = false, bool loader = true}) async {
|
||||
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;
|
||||
@ -81,7 +90,9 @@ class TimetableController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
await _fetchWeek(week, context: context).timeout(const Duration(seconds: 5), onTimeout: (() => throw "timeout"));
|
||||
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");
|
||||
@ -95,7 +106,9 @@ class TimetableController extends ChangeNotifier {
|
||||
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);
|
||||
if (skip &&
|
||||
(days?.length ?? 0) > 0 &&
|
||||
days!.last.last.end.isBefore(DateTime.now())) return next(context);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
@ -106,7 +119,9 @@ class TimetableController extends ChangeNotifier {
|
||||
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();
|
||||
if (!_differentDate(week.start, Week.fromId(0).start)) {
|
||||
week.start = TimetableController.getSchoolYearStart();
|
||||
}
|
||||
|
||||
currentWeek = week;
|
||||
previousWeekId = currentWeekId;
|
||||
@ -124,16 +139,17 @@ class TimetableController extends ChangeNotifier {
|
||||
List<List<Lesson>> _sortDays(Week week, {required BuildContext context}) {
|
||||
List<List<Lesson>> days = [];
|
||||
|
||||
try {
|
||||
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([]);
|
||||
if (days.last.isNotEmpty &&
|
||||
_differentDate(lesson.date, days.last.last.date)) days.add([]);
|
||||
days.last.add(lesson);
|
||||
}
|
||||
|
||||
@ -152,7 +168,8 @@ class TimetableController extends ChangeNotifier {
|
||||
|
||||
if (lessonIndexes.isNotEmpty) {
|
||||
// Fill missing indexes with empty spaces
|
||||
for (var i in List<int>.generate(maxIndex - minIndex + 1, (int i) => minIndex + i)) {
|
||||
for (var i in List<int>.generate(
|
||||
maxIndex - minIndex + 1, (int i) => minIndex + i)) {
|
||||
List<Lesson> indexLessons = _getLessonsByIndex(_day, i);
|
||||
|
||||
// Empty lesson
|
||||
@ -160,8 +177,13 @@ class TimetableController extends ChangeNotifier {
|
||||
// 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()}));
|
||||
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) {}
|
||||
}
|
||||
@ -171,7 +193,8 @@ class TimetableController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Additional lessons
|
||||
day.addAll(_day.where((l) => int.tryParse(l.lessonIndex) == null && l.subject.id != ''));
|
||||
day.addAll(_day.where((l) =>
|
||||
int.tryParse(l.lessonIndex) == null && l.subject.id != ''));
|
||||
|
||||
day.sort((a, b) => a.start.compareTo(b.start));
|
||||
|
||||
@ -213,5 +236,6 @@ class TimetableController extends ChangeNotifier {
|
||||
return indexes;
|
||||
}
|
||||
|
||||
bool _differentDate(DateTime a, DateTime b) => !(a.year == b.year && a.month == b.month && a.day == b.day);
|
||||
bool _differentDate(DateTime a, DateTime b) =>
|
||||
!(a.year == b.year && a.month == b.month && a.day == b.day);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import "category.dart";
|
||||
import "subject.dart";
|
||||
import "teacher.dart";
|
||||
|
||||
class Absence {
|
||||
Map? json;
|
||||
@ -7,7 +8,7 @@ class Absence {
|
||||
DateTime date;
|
||||
int delay;
|
||||
DateTime submitDate;
|
||||
String teacher;
|
||||
Teacher teacher;
|
||||
Justification state;
|
||||
Category? justification;
|
||||
Category? type;
|
||||
@ -41,8 +42,12 @@ class Absence {
|
||||
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);
|
||||
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);
|
||||
@ -51,23 +56,30 @@ class Absence {
|
||||
|
||||
return Absence(
|
||||
id: json["Uid"],
|
||||
date: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0),
|
||||
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(),
|
||||
submitDate: json["KeszitesDatuma"] != null
|
||||
? DateTime.parse(json["KeszitesDatuma"]).toLocal()
|
||||
: DateTime(0),
|
||||
teacher: Teacher.fromString((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,
|
||||
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"] : "",
|
||||
group:
|
||||
json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] : "",
|
||||
json: json,
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'category.dart';
|
||||
import 'teacher.dart';
|
||||
|
||||
class Exam {
|
||||
Map? json;
|
||||
@ -7,7 +8,7 @@ class Exam {
|
||||
Category? mode;
|
||||
int? subjectIndex;
|
||||
String subjectName;
|
||||
String teacher;
|
||||
Teacher teacher;
|
||||
String description;
|
||||
String group;
|
||||
String id;
|
||||
@ -28,14 +29,20 @@ class Exam {
|
||||
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),
|
||||
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(),
|
||||
teacher: Teacher.fromString((json["RogzitoTanarNeve"] ?? "").trim()),
|
||||
description: (json["Temaja"] ?? "").trim(),
|
||||
group: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] ?? "" : "",
|
||||
group: json["OsztalyCsoport"] != null
|
||||
? json["OsztalyCsoport"]["Uid"] ?? ""
|
||||
: "",
|
||||
json: json,
|
||||
);
|
||||
}
|
||||
|