Merge pull request #31 from refilc/dev

everything from dev
This commit is contained in:
Márton Kiss 2023-09-02 21:03:49 +02:00 committed by GitHub
commit 79370f6a02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
126 changed files with 7259 additions and 2482 deletions

16
.gitignore vendored
View File

@ -1,5 +1,7 @@
# See https://www.dartlang.org/guides/libraries/private-files # See https://www.dartlang.org/guides/libraries/private-files
.gitignore
termek.txt termek.txt
.DS_Store .DS_Store
filc3.properties filc3.properties
@ -25,9 +27,6 @@ doc/api/
*.js.map *.js.map
*.txt *.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/macos/Flutter/GeneratedPluginRegistrant.swift
filcnaplo/windows/flutter/ephemeral/.plugin_symlinks/connectivity_plus filcnaplo/windows/flutter/ephemeral/.plugin_symlinks/connectivity_plus
filcnaplo/windows/flutter/ephemeral/.plugin_symlinks/dynamic_color 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.cc
filcnaplo/linux/flutter/generated_plugin_registrant.h filcnaplo/linux/flutter/generated_plugin_registrant.h
filcnaplo/linux/flutter/generated_plugins.cmake filcnaplo/linux/flutter/generated_plugins.cmake
filcnaplo/macos/Flutter/GeneratedPluginRegistrant.swift filcnaplo/macos/Flutter/*
filcnaplo/linux/flutter/generated_plugin_registrant.cc filcnaplo/ios/Podfile.lock
filcnaplo/linux/flutter/generated_plugin_registrant.h
filcnaplo/linux/flutter/generated_plugins.cmake
filcnaplo/macos/Flutter/GeneratedPluginRegistrant.swift
.vscode/ .vscode/
key.properties key.properties
.flutter-plugins*

1286
.idea/libraries/Dart_Packages.xml generated Normal file

File diff suppressed because it is too large Load Diff

27
.idea/libraries/Dart_SDK.xml generated Normal file
View File

@ -0,0 +1,27 @@
<component name="libraryTable">
<library name="Dart SDK">
<CLASSES>
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/async" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/cli" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/collection" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/convert" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/core" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/developer" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/ffi" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/html" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/indexed_db" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/io" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/isolate" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/js" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/js_util" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/math" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/mirrors" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/svg" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/typed_data" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/web_audio" />
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/web_gl" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

4
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Android API 33, extension level 3 Platform" project-jdk-type="Android SDK" />
</project>

View File

@ -12,7 +12,7 @@
### Clone the project ### Clone the project
```sh ```sh
git clone --recursive https://github.com/refilc/naplo git clone https://github.com/refilc/naplo
cd naplo cd naplo
``` ```

View File

@ -2,12 +2,14 @@
xmlns:tools="http://schemas.android.com/tools" package="hu.refilc.naplo"> 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" <application android:name="${applicationName}" android:label="reFilc" tools:replace="android:label" android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"> 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:launchMode="singleTop" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true" android:showWhenLocked="true"
android:turnScreenOn="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" <meta-data android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" /> android:resource="@style/NormalTheme" />
<intent-filter> <intent-filter>
@ -18,16 +20,26 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <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 <data
android:scheme="https" android:scheme="https"
android:host="api.filcnaplo.hu" android:host="api.refilcapp.hu"
android:pathPrefix="/callback" /> 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> </intent-filter>
</activity> </activity>
<meta-data android:name="flutterEmbedding" android:value="2" /> <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"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -43,7 +55,7 @@
android:resource="@xml/home_widget_test_info" /> android:resource="@xml/home_widget_test_info" />
</receiver> </receiver>
<service android:name=".widget_timetable.WidgetTimetableService" <service android:name="hu.refilc.naplo.widget_timetable.WidgetTimetableService"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver" <receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
@ -57,6 +69,8 @@
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" /> android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
</application> </application>
<meta-data android:name="flutterEmbedding" android:value="2" />
<!-- Permissions --> <!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />

View File

@ -1 +0,0 @@
../../../../../../../../../filcnaplo_premium/android/database

View File

@ -1 +0,0 @@
../../../../../../../../../filcnaplo_premium/android/utils

View File

@ -1 +0,0 @@
../../../../../../../../../filcnaplo_premium/android/widget_timetable

View File

@ -1,9 +1,7 @@
package hu.refilc.naplo; package hu.refilc.naplo;
import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine; public class MainActivity extends FlutterActivity {
public class MainActivity extends FlutterActivity { }
}

View File

@ -1,119 +1,119 @@
package hu.refilc.naplo.database; package hu.refilc.naplo.database;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import java.sql.SQLException; import java.sql.SQLException;
import hu.refilc.naplo.database.SQLiteHelper; import hu.refilc.naplo.database.SQLiteHelper;
public class DBManager { public class DBManager {
private Context context; private Context context;
private SQLiteDatabase database; private SQLiteDatabase database;
private SQLiteHelper dbHelper; private SQLiteHelper dbHelper;
public DBManager(Context c) { public DBManager(Context c) {
this.context = c; this.context = c;
} }
public DBManager open() throws SQLException { public DBManager open() throws SQLException {
this.dbHelper = new SQLiteHelper(this.context); this.dbHelper = new SQLiteHelper(this.context);
this.database = this.dbHelper.getWritableDatabase(); this.database = this.dbHelper.getWritableDatabase();
return this; return this;
} }
public void close() { public void close() {
this.dbHelper.close(); this.dbHelper.close();
} }
public Cursor fetchWidget(int wid) { public Cursor fetchWidget(int wid) {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_WIDGETS, new String[]{SQLiteHelper._ID, SQLiteHelper.DAY_SEL}, SQLiteHelper._ID + " = " + wid, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_WIDGETS, new String[]{SQLiteHelper._ID, SQLiteHelper.DAY_SEL}, SQLiteHelper._ID + " = " + wid, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public Cursor fetchTimetable() { public Cursor fetchTimetable() {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_USER_DATA, new String[]{SQLiteHelper.TIMETABLE}, null, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_USER_DATA, new String[]{SQLiteHelper.TIMETABLE}, null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public Cursor fetchLastUser() { public Cursor fetchLastUser() {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.LAST_ACCOUNT_ID}, null, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.LAST_ACCOUNT_ID}, null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public Cursor fetchTheme() { public Cursor fetchTheme() {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.THEME, SQLiteHelper.ACCENT_COLOR}, null, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.THEME, SQLiteHelper.ACCENT_COLOR}, null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public Cursor fetchPremiumToken() { public Cursor fetchPremiumToken() {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.PREMIUM_TOKEN}, null, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.PREMIUM_TOKEN}, null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public Cursor fetchPremiumScopes() { public Cursor fetchPremiumScopes() {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.PREMIUM_SCOPES}, null, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.PREMIUM_SCOPES}, null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public Cursor fetchLocale() { public Cursor fetchLocale() {
Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.LOCALE}, null, null, null, null, null); Cursor cursor = this.database.query(SQLiteHelper.TABLE_NAME_SETTINGS, new String[]{SQLiteHelper.LOCALE}, null, null, null, null, null);
if (cursor != null) { if (cursor != null) {
cursor.moveToFirst(); cursor.moveToFirst();
} }
return cursor; return cursor;
} }
public void deleteWidget(int _id) { public void deleteWidget(int _id) {
this.database.delete(SQLiteHelper.TABLE_NAME_WIDGETS, "_id=" + _id, null); this.database.delete(SQLiteHelper.TABLE_NAME_WIDGETS, "_id=" + _id, null);
} }
/*public void changeSettings(int _id, Map<String, String> map) { /*public void changeSettings(int _id, Map<String, String> map) {
ContentValues con = new ContentValues(); ContentValues con = new ContentValues();
for(Map.Entry<String, String> e: map.entrySet()){ for(Map.Entry<String, String> e: map.entrySet()){
con.put(e.getKey(), e.getValue()); con.put(e.getKey(), e.getValue());
} }
this.database.update(SQLiteHelper.TABLE_NAME_WIDGETS, con, "_id = " + _id, null); this.database.update(SQLiteHelper.TABLE_NAME_WIDGETS, con, "_id = " + _id, null);
} }
public void insertSettings(int _id, Map<String, String> map) { public void insertSettings(int _id, Map<String, String> map) {
ContentValues con = new ContentValues(); ContentValues con = new ContentValues();
for(Map.Entry<String, String> e: map.entrySet()){ for(Map.Entry<String, String> e: map.entrySet()){
con.put(e.getKey(), e.getValue()); con.put(e.getKey(), e.getValue());
//Log.d("Settings added", e.getKey() + " - " + e.getValue()); //Log.d("Settings added", e.getKey() + " - " + e.getValue());
} }
this.database.insert(SQLiteHelper.TABLE_NAME_WIDGETS, null, con); this.database.insert(SQLiteHelper.TABLE_NAME_WIDGETS, null, con);
}*/ }*/
public void insertSelDay(int _id, int day_sel) { public void insertSelDay(int _id, int day_sel) {
ContentValues con = new ContentValues(); ContentValues con = new ContentValues();
con.put(SQLiteHelper._ID, _id); con.put(SQLiteHelper._ID, _id);
con.put(SQLiteHelper.DAY_SEL, day_sel); con.put(SQLiteHelper.DAY_SEL, day_sel);
this.database.insert(SQLiteHelper.TABLE_NAME_WIDGETS, null, con); this.database.insert(SQLiteHelper.TABLE_NAME_WIDGETS, null, con);
} }
public int update(int _id, int day_sel) { public int update(int _id, int day_sel) {
ContentValues con = new ContentValues(); ContentValues con = new ContentValues();
con.put(SQLiteHelper.DAY_SEL, day_sel); con.put(SQLiteHelper.DAY_SEL, day_sel);
return this.database.update(SQLiteHelper.TABLE_NAME_WIDGETS, con, SQLiteHelper._ID + " = " + _id, null); return this.database.update(SQLiteHelper.TABLE_NAME_WIDGETS, con, SQLiteHelper._ID + " = " + _id, null);
} }
} }

View File

@ -1,36 +1,36 @@
package hu.refilc.naplo.database; package hu.refilc.naplo.database;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
public class SQLiteHelper extends SQLiteOpenHelper { public class SQLiteHelper extends SQLiteOpenHelper {
private static final String CREATE_TABLE_WIDGET = " create table widgets ( _id INTEGER NOT NULL, day_sel INTEGER NOT NULL);"; private static final String CREATE_TABLE_WIDGET = " create table widgets ( _id INTEGER NOT NULL, day_sel INTEGER NOT NULL);";
private static final String DB_NAME = "app.db"; private static final String DB_NAME = "app.db";
private static final int DB_VERSION = 1; private static final int DB_VERSION = 1;
public static final String _ID = "_id"; public static final String _ID = "_id";
public static final String DAY_SEL = "day_sel"; public static final String DAY_SEL = "day_sel";
public static final String TIMETABLE = "timetable"; public static final String TIMETABLE = "timetable";
public static final String LAST_ACCOUNT_ID = "last_account_id"; public static final String LAST_ACCOUNT_ID = "last_account_id";
public static final String THEME = "theme"; public static final String THEME = "theme";
public static final String PREMIUM_TOKEN = "premium_token"; public static final String PREMIUM_TOKEN = "premium_token";
public static final String PREMIUM_SCOPES = "premium_scopes"; public static final String PREMIUM_SCOPES = "premium_scopes";
public static final String LOCALE = "language"; public static final String LOCALE = "language";
public static final String ACCENT_COLOR = "accent_color"; public static final String ACCENT_COLOR = "accent_color";
public static final String TABLE_NAME_WIDGETS = "widgets"; public static final String TABLE_NAME_WIDGETS = "widgets";
public static final String TABLE_NAME_USER_DATA = "user_data"; public static final String TABLE_NAME_USER_DATA = "user_data";
public static final String TABLE_NAME_SETTINGS = "settings"; public static final String TABLE_NAME_SETTINGS = "settings";
public SQLiteHelper(Context context) { public SQLiteHelper(Context context) {
super(context, DB_NAME, null, 7); super(context, DB_NAME, null, 7);
} }
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_WIDGET); db.execSQL(CREATE_TABLE_WIDGET);
} }
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS widgets"); db.execSQL("DROP TABLE IF EXISTS widgets");
onCreate(db); onCreate(db);
} }
} }

View File

@ -1,36 +1,36 @@
package hu.refilc.naplo.utils; package hu.refilc.naplo.utils;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
public class Utils { public class Utils {
public static boolean hasNetwork(Context context) { public static boolean hasNetwork(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo(); NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnectedOrConnecting()) { if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true; return true;
} }
return false; return false;
} }
public static Date getWeekStartDate() { public static Date getWeekStartDate() {
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) { while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
calendar.add(Calendar.DATE, -1); calendar.add(Calendar.DATE, -1);
} }
return calendar.getTime(); return calendar.getTime();
} }
public static Date getWeekEndDate() { public static Date getWeekEndDate() {
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) { while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
calendar.add(Calendar.DATE, 1); calendar.add(Calendar.DATE, 1);
} }
calendar.add(Calendar.DATE, -1); calendar.add(Calendar.DATE, -1);
return calendar.getTime(); return calendar.getTime();
} }
} }

View File

@ -1,65 +1,65 @@
package hu.refilc.naplo.utils; package hu.refilc.naplo.utils;
import java.time.DayOfWeek; import java.time.DayOfWeek;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
public class Week { public class Week {
private final LocalDate start; private final LocalDate start;
private final LocalDate end; private final LocalDate end;
private Week(LocalDate start, LocalDate end) { private Week(LocalDate start, LocalDate end) {
this.start = start; this.start = start;
this.end = end; this.end = end;
} }
public static Week current() { public static Week current() {
return fromDate(LocalDate.now()); return fromDate(LocalDate.now());
} }
public static Week fromId(int id) { public static Week fromId(int id) {
LocalDate _now = getYearStart().plusDays(id * 7L); LocalDate _now = getYearStart().plusDays(id * 7L);
return new Week(_now.minusDays(_now.getDayOfWeek().getValue() - 1), _now.plusDays(7 - _now.getDayOfWeek().getValue())); return new Week(_now.minusDays(_now.getDayOfWeek().getValue() - 1), _now.plusDays(7 - _now.getDayOfWeek().getValue()));
} }
public static Week fromDate(LocalDate date) { public static Week fromDate(LocalDate date) {
return new Week(date.minusDays(date.getDayOfWeek().getValue() - 1), date.plusDays(7 - date.getDayOfWeek().getValue())); return new Week(date.minusDays(date.getDayOfWeek().getValue() - 1), date.plusDays(7 - date.getDayOfWeek().getValue()));
} }
public Week next() { public Week next() {
return Week.fromDate(start.plusDays(8)); return Week.fromDate(start.plusDays(8));
} }
public int id() { public int id() {
return (int) Math.ceil(Duration.between(getYearStart().atStartOfDay(), start.atStartOfDay()).toDays() / 7f); return (int) Math.ceil(Duration.between(getYearStart().atStartOfDay(), start.atStartOfDay()).toDays() / 7f);
} }
private static LocalDate getYearStart() { private static LocalDate getYearStart() {
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
LocalDate start = getYearStart(now.getYear()); LocalDate start = getYearStart(now.getYear());
return start.isBefore(now) ? start : getYearStart(now.getYear() -1); return start.isBefore(now) ? start : getYearStart(now.getYear() -1);
} }
private static LocalDate getYearStart(int year) { private static LocalDate getYearStart(int year) {
LocalDate time = LocalDate.of(year, 9, 1); LocalDate time = LocalDate.of(year, 9, 1);
if (time.getDayOfWeek() == DayOfWeek.SATURDAY) if (time.getDayOfWeek() == DayOfWeek.SATURDAY)
return time.plusDays(2); return time.plusDays(2);
else if (time.getDayOfWeek() == DayOfWeek.SUNDAY) else if (time.getDayOfWeek() == DayOfWeek.SUNDAY)
return time.plusDays(1); return time.plusDays(1);
return time; return time;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Week week = (Week) o; Week week = (Week) o;
return this.id() == week.id(); return this.id() == week.id();
} }
@Override @Override
public int hashCode() { public int hashCode() {
return id(); return id();
} }
} }

View File

@ -1,397 +1,397 @@
package hu.refilc.naplo.widget_timetable; package hu.refilc.naplo.widget_timetable;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider; import android.appwidget.AppWidgetProvider;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import android.widget.Toast; import android.widget.Toast;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.time.DayOfWeek; import java.time.DayOfWeek;
import java.time.format.TextStyle; import java.time.format.TextStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import hu.refilc.naplo.database.DBManager; import hu.refilc.naplo.database.DBManager;
import hu.refilc.naplo.MainActivity; import hu.refilc.naplo.MainActivity;
import hu.refilc.naplo.R; import hu.refilc.naplo.R;
import hu.refilc.naplo.utils.Week; import hu.refilc.naplo.utils.Week;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import es.antonborri.home_widget.HomeWidgetBackgroundIntent; import es.antonborri.home_widget.HomeWidgetBackgroundIntent;
import es.antonborri.home_widget.HomeWidgetLaunchIntent; import es.antonborri.home_widget.HomeWidgetLaunchIntent;
import es.antonborri.home_widget.HomeWidgetProvider; import es.antonborri.home_widget.HomeWidgetProvider;
public class WidgetTimetable extends HomeWidgetProvider { public class WidgetTimetable extends HomeWidgetProvider {
private static final String ACTION_WIDGET_CLICK_NAV_LEFT = "list_widget.ACTION_WIDGET_CLICK_NAV_LEFT"; private static final String ACTION_WIDGET_CLICK_NAV_LEFT = "list_widget.ACTION_WIDGET_CLICK_NAV_LEFT";
private static final String ACTION_WIDGET_CLICK_NAV_RIGHT = "list_widget.ACTION_WIDGET_CLICK_NAV_RIGHT"; private static final String ACTION_WIDGET_CLICK_NAV_RIGHT = "list_widget.ACTION_WIDGET_CLICK_NAV_RIGHT";
private static final String ACTION_WIDGET_CLICK_NAV_TODAY = "list_widget.ACTION_WIDGET_CLICK_NAV_TODAY"; private static final String ACTION_WIDGET_CLICK_NAV_TODAY = "list_widget.ACTION_WIDGET_CLICK_NAV_TODAY";
private static final String ACTION_WIDGET_CLICK_NAV_REFRESH = "list_widget.ACTION_WIDGET_CLICK_NAV_REFRESH"; private static final String ACTION_WIDGET_CLICK_NAV_REFRESH = "list_widget.ACTION_WIDGET_CLICK_NAV_REFRESH";
private static final String ACTION_WIDGET_CLICK_BUY_PREMIUM = "list_widget.ACTION_WIDGET_CLICK_BUY_PREMIUM"; private static final String ACTION_WIDGET_CLICK_BUY_PREMIUM = "list_widget.ACTION_WIDGET_CLICK_BUY_PREMIUM";
@Override @Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, SharedPreferences widgetData) { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, SharedPreferences widgetData) {
for (int i = 0; i < appWidgetIds.length; i++) { for (int i = 0; i < appWidgetIds.length; i++) {
RemoteViews views = generateView(context, appWidgetIds[i]); RemoteViews views = generateView(context, appWidgetIds[i]);
if(premiumEnabled(context) && userLoggedIn(context)) { if(premiumEnabled(context) && userLoggedIn(context)) {
int rday = selectDay(context, appWidgetIds[i], 0, true); int rday = selectDay(context, appWidgetIds[i], 0, true);
views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday)); views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday));
} }
pushUpdate(context, views, appWidgetIds[i]); pushUpdate(context, views, appWidgetIds[i]);
} }
} }
public static void pushUpdate(Context context, RemoteViews remoteViews, int appWidgetSingleId) { public static void pushUpdate(Context context, RemoteViews remoteViews, int appWidgetSingleId) {
AppWidgetManager manager = AppWidgetManager.getInstance(context); AppWidgetManager manager = AppWidgetManager.getInstance(context);
manager.updateAppWidget(appWidgetSingleId, remoteViews); manager.updateAppWidget(appWidgetSingleId, remoteViews);
manager.notifyAppWidgetViewDataChanged(appWidgetSingleId, R.id.widget_list); manager.notifyAppWidgetViewDataChanged(appWidgetSingleId, R.id.widget_list);
} }
public static RemoteViews generateView(Context context, int appId) { public static RemoteViews generateView(Context context, int appId) {
Intent serviceIntent = new Intent(context, WidgetTimetableService.class); Intent serviceIntent = new Intent(context, WidgetTimetableService.class);
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appId); serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appId);
serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME))); serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_timetable); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_timetable);
views.setViewVisibility(R.id.need_premium, View.GONE); views.setViewVisibility(R.id.need_premium, View.GONE);
views.setViewVisibility(R.id.need_login, View.GONE); views.setViewVisibility(R.id.need_login, View.GONE);
views.setViewVisibility(R.id.tt_grid_cont, View.GONE); views.setViewVisibility(R.id.tt_grid_cont, View.GONE);
if(!userLoggedIn(context)) { if(!userLoggedIn(context)) {
views.setViewVisibility(R.id.need_login, View.VISIBLE); views.setViewVisibility(R.id.need_login, View.VISIBLE);
views.setOnClickPendingIntent(R.id.open_login, makePending(context, ACTION_WIDGET_CLICK_BUY_PREMIUM, appId)); views.setOnClickPendingIntent(R.id.open_login, makePending(context, ACTION_WIDGET_CLICK_BUY_PREMIUM, appId));
} else if(premiumEnabled(context)) { } else if(premiumEnabled(context)) {
views.setViewVisibility(R.id.tt_grid_cont, View.VISIBLE); views.setViewVisibility(R.id.tt_grid_cont, View.VISIBLE);
views.setOnClickPendingIntent(R.id.nav_to_left, makePending(context, ACTION_WIDGET_CLICK_NAV_LEFT, appId)); views.setOnClickPendingIntent(R.id.nav_to_left, makePending(context, ACTION_WIDGET_CLICK_NAV_LEFT, appId));
views.setOnClickPendingIntent(R.id.nav_to_right, makePending(context, ACTION_WIDGET_CLICK_NAV_RIGHT, appId)); views.setOnClickPendingIntent(R.id.nav_to_right, makePending(context, ACTION_WIDGET_CLICK_NAV_RIGHT, appId));
views.setOnClickPendingIntent(R.id.nav_current, makePending(context, ACTION_WIDGET_CLICK_NAV_TODAY, appId)); views.setOnClickPendingIntent(R.id.nav_current, makePending(context, ACTION_WIDGET_CLICK_NAV_TODAY, appId));
views.setOnClickPendingIntent(R.id.nav_refresh, makePending(context, ACTION_WIDGET_CLICK_NAV_REFRESH, appId)); views.setOnClickPendingIntent(R.id.nav_refresh, makePending(context, ACTION_WIDGET_CLICK_NAV_REFRESH, appId));
views.setRemoteAdapter(R.id.widget_list, serviceIntent); views.setRemoteAdapter(R.id.widget_list, serviceIntent);
views.setEmptyView(R.id.widget_list, R.id.empty_view); views.setEmptyView(R.id.widget_list, R.id.empty_view);
} else { } else {
views.setViewVisibility(R.id.need_premium, View.VISIBLE); views.setViewVisibility(R.id.need_premium, View.VISIBLE);
views.setOnClickPendingIntent(R.id.buy_premium, makePending(context, ACTION_WIDGET_CLICK_BUY_PREMIUM, appId)); views.setOnClickPendingIntent(R.id.buy_premium, makePending(context, ACTION_WIDGET_CLICK_BUY_PREMIUM, appId));
} }
return views; return views;
} }
static PendingIntent makePending(Context context, String action, int appWidgetId) { static PendingIntent makePending(Context context, String action, int appWidgetId) {
Intent activebtnnext = new Intent(context, WidgetTimetable.class); Intent activebtnnext = new Intent(context, WidgetTimetable.class);
activebtnnext.setAction(action); activebtnnext.setAction(action);
activebtnnext.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); activebtnnext.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
return PendingIntent.getBroadcast(context, appWidgetId, activebtnnext , PendingIntent.FLAG_IMMUTABLE); return PendingIntent.getBroadcast(context, appWidgetId, activebtnnext , PendingIntent.FLAG_IMMUTABLE);
} }
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent); super.onReceive(context, intent);
if(intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { if(intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
int appId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); int appId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
RemoteViews views = generateView(context, appId); RemoteViews views = generateView(context, appId);
try { try {
if(premiumEnabled(context) && userLoggedIn(context)) { if(premiumEnabled(context) && userLoggedIn(context)) {
if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_LEFT)) { if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_LEFT)) {
int rday = selectDay(context, appId, -1, false); int rday = selectDay(context, appId, -1, false);
views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday)); views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday));
pushUpdate(context, views, appId); pushUpdate(context, views, appId);
} else if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_RIGHT)) { } else if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_RIGHT)) {
int rday = selectDay(context, appId, 1, false); int rday = selectDay(context, appId, 1, false);
views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday)); views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday));
pushUpdate(context, views, appId); pushUpdate(context, views, appId);
} else if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_TODAY)) { } else if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_TODAY)) {
int rday = getToday(context); int rday = getToday(context);
setSelectedDay(context, appId, rday); setSelectedDay(context, appId, rday);
views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday)); views.setTextViewText(R.id.nav_current, convertDayOfWeek(context, rday));
pushUpdate(context, views, appId); pushUpdate(context, views, appId);
} else if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_REFRESH)) { } else if (intent.getAction().equals(ACTION_WIDGET_CLICK_NAV_REFRESH)) {
PendingIntent pendingIntent = HomeWidgetLaunchIntent.INSTANCE.getActivity(context, MainActivity.class, Uri.parse("timetable://refresh")); PendingIntent pendingIntent = HomeWidgetLaunchIntent.INSTANCE.getActivity(context, MainActivity.class, Uri.parse("timetable://refresh"));
pendingIntent.send(); pendingIntent.send();
} else if (intent.getAction().equals("android.appwidget.action.APPWIDGET_DELETED")) { } else if (intent.getAction().equals("android.appwidget.action.APPWIDGET_DELETED")) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
dbManager.deleteWidget(appId); dbManager.deleteWidget(appId);
dbManager.close(); dbManager.close();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
if(intent.getAction().equals(ACTION_WIDGET_CLICK_BUY_PREMIUM)) { if(intent.getAction().equals(ACTION_WIDGET_CLICK_BUY_PREMIUM)) {
PendingIntent pendingIntent = HomeWidgetLaunchIntent.INSTANCE.getActivity(context, MainActivity.class, Uri.parse("settings://premium")); PendingIntent pendingIntent = HomeWidgetLaunchIntent.INSTANCE.getActivity(context, MainActivity.class, Uri.parse("settings://premium"));
pendingIntent.send(); pendingIntent.send();
} }
} }
catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
public static String convertDayOfWeek(Context context, int rday) { public static String convertDayOfWeek(Context context, int rday) {
/*if(rday == -1) return DayOfWeek.of(1).getDisplayName(TextStyle.FULL, new Locale("hu", "HU")); /*if(rday == -1) return DayOfWeek.of(1).getDisplayName(TextStyle.FULL, new Locale("hu", "HU"));
String dayOfWeek = DayOfWeek.of(rday + 1).getDisplayName(TextStyle.FULL, new Locale("hu", "HU"));*/ String dayOfWeek = DayOfWeek.of(rday + 1).getDisplayName(TextStyle.FULL, new Locale("hu", "HU"));*/
String dayOfWeek = "Unknown"; String dayOfWeek = "Unknown";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Locale loc = getLocale(context); Locale loc = getLocale(context);
if (rday == -1) if (rday == -1)
return DayOfWeek.of(1).getDisplayName(TextStyle.FULL, loc); return DayOfWeek.of(1).getDisplayName(TextStyle.FULL, loc);
dayOfWeek = DayOfWeek.of(rday + 1).getDisplayName(TextStyle.FULL, loc); dayOfWeek = DayOfWeek.of(rday + 1).getDisplayName(TextStyle.FULL, loc);
} }
return dayOfWeek.substring(0, 1).toUpperCase() + dayOfWeek.substring(1).toLowerCase(); return dayOfWeek.substring(0, 1).toUpperCase() + dayOfWeek.substring(1).toLowerCase();
} }
public static void setSelectedDay(Context context, int wid, int day) { public static void setSelectedDay(Context context, int wid, int day) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
dbManager.update(wid, day); dbManager.update(wid, day);
dbManager.close(); dbManager.close();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public static int getToday(Context context) { public static int getToday(Context context) {
int rday = new DateTime().getDayOfWeek() - 1; int rday = new DateTime().getDayOfWeek() - 1;
List<JSONArray> s = genJsonDays(context); List<JSONArray> s = genJsonDays(context);
try { try {
if(checkIsAfter(s, rday)) rday += 1; if(checkIsAfter(s, rday)) rday += 1;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return retDay(rday, s.size()); return retDay(rday, s.size());
} }
public static int selectDay(Context context, int wid, int add, Boolean afterSubjects) { public static int selectDay(Context context, int wid, int add, Boolean afterSubjects) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
Cursor cursor = dbManager.fetchWidget(wid); Cursor cursor = dbManager.fetchWidget(wid);
List<JSONArray> s = genJsonDays(context); List<JSONArray> s = genJsonDays(context);
int retday = new DateTime().getDayOfWeek() - 1; int retday = new DateTime().getDayOfWeek() - 1;
if(cursor.getCount() != 0) retday = retDay(cursor.getInt(1) + add, s.size()); if(cursor.getCount() != 0) retday = retDay(cursor.getInt(1) + add, s.size());
if(afterSubjects) if(checkIsAfter(s, retday)) retday += 1; if(afterSubjects) if(checkIsAfter(s, retday)) retday += 1;
retday = retDay(retday, s.size()); retday = retDay(retday, s.size());
if(cursor.getCount() == 0) dbManager.insertSelDay(wid, retday); if(cursor.getCount() == 0) dbManager.insertSelDay(wid, retday);
else dbManager.update(wid, retday); else dbManager.update(wid, retday);
dbManager.close(); dbManager.close();
return retday; return retday;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return 0; return 0;
} }
public static Boolean checkIsAfter(List<JSONArray> s, int retday) throws Exception { public static Boolean checkIsAfter(List<JSONArray> s, int retday) throws Exception {
retday = retDay(retday, s.size()); retday = retDay(retday, s.size());
String vegIdopont = s.get(retday).getJSONObject(s.get(retday).length() - 1).getString("VegIdopont"); String vegIdopont = s.get(retday).getJSONObject(s.get(retday).length() - 1).getString("VegIdopont");
return new DateTime().isAfter(new DateTime(vegIdopont)); return new DateTime().isAfter(new DateTime(vegIdopont));
} }
public static int retDay(int retday, int size) { public static int retDay(int retday, int size) {
if (retday < 0) retday = size - 1; if (retday < 0) retday = size - 1;
else if (retday > size - 1) retday = 0; else if (retday > size - 1) retday = 0;
return retday; return retday;
} }
public static List<JSONArray> genJsonDays(Context context) { public static List<JSONArray> genJsonDays(Context context) {
List<JSONArray> gen_days = new ArrayList<>(); List<JSONArray> gen_days = new ArrayList<>();
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
Cursor ct = dbManager.fetchTimetable(); Cursor ct = dbManager.fetchTimetable();
dbManager.close(); dbManager.close();
if(ct.getCount() == 0) { if(ct.getCount() == 0) {
return gen_days; return gen_days;
} }
JSONObject fecthtt = new JSONObject(ct.getString(0)); JSONObject fecthtt = new JSONObject(ct.getString(0));
JSONArray dayArray = new JSONArray(); JSONArray dayArray = new JSONArray();
String currday = ""; String currday = "";
String currweek = String.valueOf(Week.current().id()); String currweek = String.valueOf(Week.current().id());
JSONArray week = fecthtt.getJSONArray(currweek); JSONArray week = fecthtt.getJSONArray(currweek);
for (int i=0; i < week.length(); i++) for (int i=0; i < week.length(); i++)
{ {
try { try {
if(i == 0) currday = week.getJSONObject(0).getString("Datum"); if(i == 0) currday = week.getJSONObject(0).getString("Datum");
JSONObject oraObj = week.getJSONObject(i); JSONObject oraObj = week.getJSONObject(i);
if(!currday.equals(oraObj.getString("Datum"))) { if(!currday.equals(oraObj.getString("Datum"))) {
gen_days.add(dayArray); gen_days.add(dayArray);
currday = week.getJSONObject(i).getString("Datum"); currday = week.getJSONObject(i).getString("Datum");
dayArray = new JSONArray(); dayArray = new JSONArray();
} }
dayArray.put(oraObj); dayArray.put(oraObj);
if(i == week.length() - 1) { if(i == week.length() - 1) {
gen_days.add(dayArray); gen_days.add(dayArray);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
Collections.sort(gen_days, new Comparator<JSONArray>() { Collections.sort(gen_days, new Comparator<JSONArray>() {
public int compare(JSONArray a, JSONArray b) { public int compare(JSONArray a, JSONArray b) {
long valA = 0; long valA = 0;
long valB = 0; long valB = 0;
try { try {
valA = (long) new DateTime( a.getJSONObject(0).getString("Datum")).getMillis(); valA = (long) new DateTime( a.getJSONObject(0).getString("Datum")).getMillis();
valB = (long) new DateTime( b.getJSONObject(0).getString("Datum")).getMillis(); valB = (long) new DateTime( b.getJSONObject(0).getString("Datum")).getMillis();
} }
catch (JSONException ignored) { catch (JSONException ignored) {
} }
return (int) (valA - valB); return (int) (valA - valB);
} }
}); });
return gen_days; return gen_days;
} }
public static String zeroPad(int value, int padding){ public static String zeroPad(int value, int padding){
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(value); b.append(value);
while(b.length() < padding){ while(b.length() < padding){
b.insert(0,"0"); b.insert(0,"0");
} }
return b.toString(); return b.toString();
} }
public static Locale getLocale(Context context) { public static Locale getLocale(Context context) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
String loc = dbManager.fetchLocale().getString(0); String loc = dbManager.fetchLocale().getString(0);
dbManager.close(); dbManager.close();
if(loc.equals("hu") || loc.equals("de")) { if(loc.equals("hu") || loc.equals("de")) {
return new Locale(loc, loc.toUpperCase()); return new Locale(loc, loc.toUpperCase());
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return new Locale("en", "GB"); return new Locale("en", "GB");
} }
public static boolean premiumEnabled(Context context) { public static boolean premiumEnabled(Context context) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
String premium_token = dbManager.fetchPremiumToken().getString(0); String premium_token = dbManager.fetchPremiumToken().getString(0);
String premium_scopes_raw = dbManager.fetchPremiumScopes().getString(0); String premium_scopes_raw = dbManager.fetchPremiumScopes().getString(0);
dbManager.close(); dbManager.close();
JSONArray arr = new JSONArray(premium_scopes_raw); JSONArray arr = new JSONArray(premium_scopes_raw);
List<String> premium_scopes = new ArrayList<>(); List<String> premium_scopes = new ArrayList<>();
for(int i = 0; i < arr.length(); i++){ for(int i = 0; i < arr.length(); i++){
String scope = arr.getString(i); String scope = arr.getString(i);
premium_scopes.add(scope.substring(scope.lastIndexOf('.') + 1)); premium_scopes.add(scope.substring(scope.lastIndexOf('.') + 1));
} }
if(!premium_token.equals("") && (premium_scopes.contains("*") || premium_scopes.contains("TIMETALBE_WIDGET"))) { if(!premium_token.equals("") && (premium_scopes.contains("*") || premium_scopes.contains("TIMETALBE_WIDGET"))) {
return true; return true;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return false; return false;
} }
public static boolean userLoggedIn(Context context) { public static boolean userLoggedIn(Context context) {
return !lastUserId(context).equals(""); return !lastUserId(context).equals("");
} }
public static String lastUserId(Context context) { public static String lastUserId(Context context) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
Cursor cursor = dbManager.fetchLastUser(); Cursor cursor = dbManager.fetchLastUser();
dbManager.close(); dbManager.close();
if(cursor != null && !cursor.getString(0).equals("")) { if(cursor != null && !cursor.getString(0).equals("")) {
String last_user = cursor.getString(0); String last_user = cursor.getString(0);
return last_user; return last_user;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return ""; return "";
} }
@Override @Override
public void onEnabled(Context context) { public void onEnabled(Context context) {
} }
@Override @Override
public void onDisabled(Context context) { public void onDisabled(Context context) {
} }
} }

View File

@ -1,354 +1,354 @@
package hu.refilc.naplo.widget_timetable; package hu.refilc.naplo.widget_timetable;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import android.widget.RemoteViewsService; import android.widget.RemoteViewsService;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import hu.refilc.naplo.database.DBManager; import hu.refilc.naplo.database.DBManager;
import hu.refilc.naplo.R; import hu.refilc.naplo.R;
public class WidgetTimetableDataProvider implements RemoteViewsService.RemoteViewsFactory { public class WidgetTimetableDataProvider implements RemoteViewsService.RemoteViewsFactory {
private Context context; private Context context;
private int appWidgetId; private int appWidgetId;
private int rday = 0; private int rday = 0;
private int theme; private int theme;
private Integer[] colorValues; private Integer[] colorValues;
List<Lesson> day_subjects = new ArrayList<>(); List<Lesson> day_subjects = new ArrayList<>();
List<Integer> lessonIndexes = new ArrayList<>(); List<Integer> lessonIndexes = new ArrayList<>();
Item witem; Item witem;
/* Default values */ /* Default values */
static class Item { static class Item {
int Layout; int Layout;
int NumVisibility; int NumVisibility;
int NameVisibility; int NameVisibility;
int NameNodescVisibility; int NameNodescVisibility;
int DescVisibility; int DescVisibility;
int RoomVisibility; int RoomVisibility;
int TimeVisibility; int TimeVisibility;
int NumColor; int NumColor;
int NameColor; int NameColor;
int NameNodescColor; int NameNodescColor;
int DescColor; int DescColor;
Integer[] NameNodescPadding = {0, 0, 0, 0}; Integer[] NameNodescPadding = {0, 0, 0, 0};
public Item(int Layout, int NumVisibility,int NameVisibility,int NameNodescVisibility,int DescVisibility,int RoomVisibility,int TimeVisibility,int NumColor,int NameColor,int NameNodescColor,int DescColor) { public Item(int Layout, int NumVisibility,int NameVisibility,int NameNodescVisibility,int DescVisibility,int RoomVisibility,int TimeVisibility,int NumColor,int NameColor,int NameNodescColor,int DescColor) {
this.Layout = Layout; this.Layout = Layout;
this.NumVisibility = NumVisibility; this.NumVisibility = NumVisibility;
this.NameVisibility = NameVisibility; this.NameVisibility = NameVisibility;
this.NameNodescVisibility = NameNodescVisibility; this.NameNodescVisibility = NameNodescVisibility;
this.DescVisibility = DescVisibility; this.DescVisibility = DescVisibility;
this.RoomVisibility = RoomVisibility; this.RoomVisibility = RoomVisibility;
this.TimeVisibility = TimeVisibility; this.TimeVisibility = TimeVisibility;
this.NumColor = NumColor; this.NumColor = NumColor;
this.NameColor = NameColor; this.NameColor = NameColor;
this.NameNodescColor = NameNodescColor; this.NameNodescColor = NameNodescColor;
this.DescColor = DescColor; this.DescColor = DescColor;
} }
} }
static class Lesson { static class Lesson {
String status; String status;
String lessonIndex; String lessonIndex;
String lessonName; String lessonName;
String lessonTopic; String lessonTopic;
String lessonRoom; String lessonRoom;
long lessonStart; long lessonStart;
long lessonEnd; long lessonEnd;
String substituteTeacher; String substituteTeacher;
public Lesson(String status, String lessonIndex,String lessonName,String lessonTopic, String lessonRoom,long lessonStart,long lessonEnd,String substituteTeacher) { public Lesson(String status, String lessonIndex,String lessonName,String lessonTopic, String lessonRoom,long lessonStart,long lessonEnd,String substituteTeacher) {
this.status = status; this.status = status;
this.lessonIndex = lessonIndex; this.lessonIndex = lessonIndex;
this.lessonName = lessonName; this.lessonName = lessonName;
this.lessonTopic = lessonTopic; this.lessonTopic = lessonTopic;
this.lessonRoom = lessonRoom; this.lessonRoom = lessonRoom;
this.lessonStart = lessonStart; this.lessonStart = lessonStart;
this.lessonEnd = lessonEnd; this.lessonEnd = lessonEnd;
this.substituteTeacher = substituteTeacher; this.substituteTeacher = substituteTeacher;
} }
} }
Integer[] itemNameNodescPadding = {0, 0, 0, 0}; Integer[] itemNameNodescPadding = {0, 0, 0, 0};
public WidgetTimetableDataProvider(Context context, Intent intent) { public WidgetTimetableDataProvider(Context context, Intent intent) {
this.context = context; this.context = context;
this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
this.theme = getThemeAccent(context); this.theme = getThemeAccent(context);
this.colorValues = new Integer[]{R.color.filc, this.colorValues = new Integer[]{R.color.filc,
R.color.blue_shade300, R.color.blue_shade300,
R.color.green_shade300, R.color.green_shade300,
R.color.lime_shade300, R.color.lime_shade300,
R.color.yellow_shade300, R.color.yellow_shade300,
R.color.orange_shade300, R.color.orange_shade300,
R.color.red_shade300, R.color.red_shade300,
R.color.pink_shade300, R.color.pink_shade300,
R.color.purple_shade300}; R.color.purple_shade300};
} }
@Override @Override
public void onCreate() { public void onCreate() {
initData(); initData();
} }
@Override @Override
public void onDataSetChanged() { public void onDataSetChanged() {
initData(); initData();
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
} }
@Override @Override
public int getCount() { public int getCount() {
return day_subjects.size(); return day_subjects.size();
} }
public void setLayout(final RemoteViews view) { public void setLayout(final RemoteViews view) {
/* Visibilities */ /* Visibilities */
view.setViewVisibility(R.id.tt_item_num, witem.NumVisibility); view.setViewVisibility(R.id.tt_item_num, witem.NumVisibility);
view.setViewVisibility(R.id.tt_item_name, witem.NameVisibility); view.setViewVisibility(R.id.tt_item_name, witem.NameVisibility);
view.setViewVisibility(R.id.tt_item_name_nodesc, witem.NameNodescVisibility); view.setViewVisibility(R.id.tt_item_name_nodesc, witem.NameNodescVisibility);
view.setViewVisibility(R.id.tt_item_desc, witem.DescVisibility); view.setViewVisibility(R.id.tt_item_desc, witem.DescVisibility);
view.setViewVisibility(R.id.tt_item_room, witem.RoomVisibility); view.setViewVisibility(R.id.tt_item_room, witem.RoomVisibility);
view.setViewVisibility(R.id.tt_item_time, witem.TimeVisibility); view.setViewVisibility(R.id.tt_item_time, witem.TimeVisibility);
/* backgroundResources */ /* backgroundResources */
view.setInt(R.id.main_lay, "setBackgroundResource", witem.Layout); view.setInt(R.id.main_lay, "setBackgroundResource", witem.Layout);
/* Paddings */ /* Paddings */
view.setViewPadding(R.id.tt_item_name_nodesc, witem.NameNodescPadding[0], witem.NameNodescPadding[1], witem.NameNodescPadding[2], witem.NameNodescPadding[3]); view.setViewPadding(R.id.tt_item_name_nodesc, witem.NameNodescPadding[0], witem.NameNodescPadding[1], witem.NameNodescPadding[2], witem.NameNodescPadding[3]);
/* Text Colors */ /* Text Colors */
view.setInt(R.id.tt_item_num, "setTextColor", getColor(context, witem.NumColor)); view.setInt(R.id.tt_item_num, "setTextColor", getColor(context, witem.NumColor));
view.setInt(R.id.tt_item_name, "setTextColor", getColor(context, witem.NameColor)); view.setInt(R.id.tt_item_name, "setTextColor", getColor(context, witem.NameColor));
view.setInt(R.id.tt_item_name_nodesc, "setTextColor", getColor(context, witem.NameNodescColor)); view.setInt(R.id.tt_item_name_nodesc, "setTextColor", getColor(context, witem.NameNodescColor));
view.setInt(R.id.tt_item_desc, "setTextColor", getColor(context, witem.DescColor)); view.setInt(R.id.tt_item_desc, "setTextColor", getColor(context, witem.DescColor));
} }
public int getColor(Context context, int color) { public int getColor(Context context, int color) {
return context.getResources().getColor(color); return context.getResources().getColor(color);
} }
@Override @Override
public RemoteViews getViewAt(int position) { public RemoteViews getViewAt(int position) {
RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.timetable_item); RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.timetable_item);
witem = defaultItem(theme); witem = defaultItem(theme);
Lesson curr_subject = day_subjects.get(position); Lesson curr_subject = day_subjects.get(position);
if (curr_subject.status.equals("empty")) { if (curr_subject.status.equals("empty")) {
witem.NumColor = R.color.text_miss_num; witem.NumColor = R.color.text_miss_num;
witem.TimeVisibility = View.GONE; witem.TimeVisibility = View.GONE;
witem.RoomVisibility = View.GONE; witem.RoomVisibility = View.GONE;
witem.NameNodescColor = R.color.text_miss; witem.NameNodescColor = R.color.text_miss;
} }
if (!curr_subject.substituteTeacher.equals("null")) { if (!curr_subject.substituteTeacher.equals("null")) {
witem.NumColor = R.color.yellow; witem.NumColor = R.color.yellow;
witem.Layout = R.drawable.card_layout_tile_helyetesitett; witem.Layout = R.drawable.card_layout_tile_helyetesitett;
} }
if (curr_subject.status.equals("Elmaradt")) { if (curr_subject.status.equals("Elmaradt")) {
witem.NumColor = R.color.red; witem.NumColor = R.color.red;
witem.Layout = R.drawable.card_layout_tile_elmarad; witem.Layout = R.drawable.card_layout_tile_elmarad;
} else if (curr_subject.status.equals("TanevRendjeEsemeny")) { } else if (curr_subject.status.equals("TanevRendjeEsemeny")) {
witem.NumVisibility = View.GONE; witem.NumVisibility = View.GONE;
witem.TimeVisibility = View.GONE; witem.TimeVisibility = View.GONE;
witem.RoomVisibility = View.GONE; witem.RoomVisibility = View.GONE;
witem.NameNodescPadding[0] = 50; witem.NameNodescPadding[0] = 50;
witem.NameNodescPadding[2] = 50; witem.NameNodescPadding[2] = 50;
witem.NameNodescColor = R.color.text_miss; witem.NameNodescColor = R.color.text_miss;
} }
if (curr_subject.lessonTopic.equals("null")) { if (curr_subject.lessonTopic.equals("null")) {
witem.DescVisibility = View.GONE; witem.DescVisibility = View.GONE;
witem.NameVisibility = View.GONE; witem.NameVisibility = View.GONE;
witem.NameNodescVisibility = View.VISIBLE; witem.NameNodescVisibility = View.VISIBLE;
} }
setLayout(view); setLayout(view);
String lessonIndexTrailing = curr_subject.lessonIndex.equals("+") ? "" : "."; String lessonIndexTrailing = curr_subject.lessonIndex.equals("+") ? "" : ".";
view.setTextViewText(R.id.tt_item_num, curr_subject.lessonIndex + lessonIndexTrailing); view.setTextViewText(R.id.tt_item_num, curr_subject.lessonIndex + lessonIndexTrailing);
view.setTextViewText(R.id.tt_item_name, curr_subject.lessonName); view.setTextViewText(R.id.tt_item_name, curr_subject.lessonName);
view.setTextViewText(R.id.tt_item_name_nodesc, curr_subject.lessonName); view.setTextViewText(R.id.tt_item_name_nodesc, curr_subject.lessonName);
view.setTextViewText(R.id.tt_item_desc, curr_subject.lessonTopic); view.setTextViewText(R.id.tt_item_desc, curr_subject.lessonTopic);
view.setTextViewText(R.id.tt_item_room, curr_subject.lessonRoom); view.setTextViewText(R.id.tt_item_room, curr_subject.lessonRoom);
if(curr_subject.lessonStart != 0 && curr_subject.lessonEnd != 0) if(curr_subject.lessonStart != 0 && curr_subject.lessonEnd != 0)
view.setTextViewText(R.id.tt_item_time, WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonStart).getHourOfDay(), 2) + ":" + WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonStart).getMinuteOfHour(), 2) + view.setTextViewText(R.id.tt_item_time, WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonStart).getHourOfDay(), 2) + ":" + WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonStart).getMinuteOfHour(), 2) +
"\n" + WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonEnd).getHourOfDay(), 2) + ":" + WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonEnd).getMinuteOfHour(),2)); "\n" + WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonEnd).getHourOfDay(), 2) + ":" + WidgetTimetable.zeroPad(new DateTime(curr_subject.lessonEnd).getMinuteOfHour(),2));
return view; return view;
} }
@Override @Override
public RemoteViews getLoadingView() { public RemoteViews getLoadingView() {
return null; return null;
} }
@Override @Override
public int getViewTypeCount() { public int getViewTypeCount() {
return 1; return 1;
} }
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
return position; return position;
} }
@Override @Override
public boolean hasStableIds() { public boolean hasStableIds() {
return true; return true;
} }
private void initData() { private void initData() {
theme = getThemeAccent(context); theme = getThemeAccent(context);
rday = WidgetTimetable.selectDay(context, appWidgetId, 0, false); rday = WidgetTimetable.selectDay(context, appWidgetId, 0, false);
day_subjects.clear(); day_subjects.clear();
lessonIndexes.clear(); lessonIndexes.clear();
try { try {
List<JSONArray> arr = WidgetTimetable.genJsonDays(context); List<JSONArray> arr = WidgetTimetable.genJsonDays(context);
if(arr.isEmpty()) { if(arr.isEmpty()) {
return; return;
} }
JSONArray arr_lessons = WidgetTimetable.genJsonDays(context).get(rday); JSONArray arr_lessons = WidgetTimetable.genJsonDays(context).get(rday);
for (int i = 0; i < arr_lessons.length(); i++) { for (int i = 0; i < arr_lessons.length(); i++) {
JSONObject obj_lessons = arr_lessons.getJSONObject(i); JSONObject obj_lessons = arr_lessons.getJSONObject(i);
day_subjects.add(jsonToLesson(obj_lessons)); day_subjects.add(jsonToLesson(obj_lessons));
} }
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }
if(day_subjects.size() > 0) { if(day_subjects.size() > 0) {
Collections.sort(day_subjects, new Comparator<Lesson>() { Collections.sort(day_subjects, new Comparator<Lesson>() {
public int compare(Lesson o1, Lesson o2) { public int compare(Lesson o1, Lesson o2) {
return new DateTime(o1.lessonStart).compareTo(new DateTime(o2.lessonStart)); return new DateTime(o1.lessonStart).compareTo(new DateTime(o2.lessonStart));
} }
}); });
for (int i = 0; i < day_subjects.size(); i++) { for (int i = 0; i < day_subjects.size(); i++) {
if(!day_subjects.get(i).lessonIndex.equals("+")) { if(!day_subjects.get(i).lessonIndex.equals("+")) {
lessonIndexes.add(Integer.valueOf(day_subjects.get(i).lessonIndex)); lessonIndexes.add(Integer.valueOf(day_subjects.get(i).lessonIndex));
} }
} }
if(lessonIndexes.size() > 0) { if(lessonIndexes.size() > 0) {
int lessonsChecked = Collections.min(lessonIndexes); int lessonsChecked = Collections.min(lessonIndexes);
int i = 0; int i = 0;
while(lessonsChecked < Collections.max(lessonIndexes)) { while(lessonsChecked < Collections.max(lessonIndexes)) {
if(!lessonIndexes.contains(lessonsChecked)) { if(!lessonIndexes.contains(lessonsChecked)) {
day_subjects.add(i, emptyLesson(lessonsChecked)); day_subjects.add(i, emptyLesson(lessonsChecked));
} }
lessonsChecked++; lessonsChecked++;
i++; i++;
} }
} }
} }
} }
public static Integer getThemeAccent(Context context) { public static Integer getThemeAccent(Context context) {
DBManager dbManager = new DBManager(context.getApplicationContext()); DBManager dbManager = new DBManager(context.getApplicationContext());
try { try {
dbManager.open(); dbManager.open();
Cursor cursor = dbManager.fetchTheme(); Cursor cursor = dbManager.fetchTheme();
dbManager.close(); dbManager.close();
return cursor.getInt(1); return cursor.getInt(1);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return 0; return 0;
} }
public Item defaultItem(int theme) { public Item defaultItem(int theme) {
return new Item( return new Item(
R.drawable.card_layout_tile, R.drawable.card_layout_tile,
View.VISIBLE, View.VISIBLE,
View.VISIBLE, View.VISIBLE,
View.INVISIBLE, View.INVISIBLE,
View.VISIBLE, View.VISIBLE,
View.VISIBLE, View.VISIBLE,
View.VISIBLE, View.VISIBLE,
colorValues[theme >= colorValues.length ? 0 : theme], colorValues[theme >= colorValues.length ? 0 : theme],
R.color.text, R.color.text,
R.color.text, R.color.text,
R.color.text_desc R.color.text_desc
); );
} }
public Lesson emptyLesson(int lessonIndex) { public Lesson emptyLesson(int lessonIndex) {
return new Lesson("empty", String.valueOf(lessonIndex), "Lyukasóra", "null", "null", 0, 0, "null"); return new Lesson("empty", String.valueOf(lessonIndex), "Lyukasóra", "null", "null", 0, 0, "null");
} }
public Lesson jsonToLesson(JSONObject json) { public Lesson jsonToLesson(JSONObject json) {
try { try {
return new Lesson( return new Lesson(
json.getJSONObject("Allapot").getString("Nev"), json.getJSONObject("Allapot").getString("Nev"),
!json.getString("Oraszam").equals("null") ? json.getString("Oraszam") : "+", !json.getString("Oraszam").equals("null") ? json.getString("Oraszam") : "+",
json.getString("Nev"), json.getString("Nev"),
json.getString("Tema"), json.getString("Tema"),
json.getString("TeremNeve"), json.getString("TeremNeve"),
new DateTime(json.getString("KezdetIdopont")).getMillis(), new DateTime(json.getString("KezdetIdopont")).getMillis(),
new DateTime(json.getString("VegIdopont")).getMillis(), new DateTime(json.getString("VegIdopont")).getMillis(),
json.getString("HelyettesTanarNeve") json.getString("HelyettesTanarNeve")
); );
}catch (Exception e) { }catch (Exception e) {
Log.d("Filc", "exception: " + e); Log.d("Filc", "exception: " + e);
}; };
return null; return null;
} }
} }

View File

@ -1,12 +1,12 @@
package hu.refilc.naplo.widget_timetable; package hu.refilc.naplo.widget_timetable;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.widget.RemoteViewsService; import android.widget.RemoteViewsService;
public class WidgetTimetableService extends RemoteViewsService { public class WidgetTimetableService extends RemoteViewsService {
@Override @Override
public RemoteViewsFactory onGetViewFactory(Intent intent) { public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new WidgetTimetableDataProvider(getApplicationContext(), intent); return new WidgetTimetableDataProvider(getApplicationContext(), intent);
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:id="@+id/main_lay" android:id="@+id/main_lay"
android:layout_height="50dp" android:layout_height="50dp"
@ -15,12 +16,14 @@
android:id="@+id/tt_item_num" android:id="@+id/tt_item_num"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="match_parent" 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:textSize="30sp"
android:textStyle="bold" android:textStyle="bold"
android:fontFamily="@font/montserrat_medium" tools:ignore="HardcodedText" />
android:text="1."
android:gravity="center"
android:textColor="@color/filc"></TextView>
<TextView <TextView
android:id="@+id/tt_item_name" android:id="@+id/tt_item_name"
@ -33,10 +36,11 @@
android:text="Óra neve" android:text="Óra neve"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:layout_toLeftOf="@id/tt_item_room" android:layout_toStartOf="@id/tt_item_room"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_toRightOf="@id/tt_item_num" android:layout_toEndOf="@id/tt_item_num"
android:textColor="@color/text"></TextView> android:textColor="@color/text"
tools:ignore="HardcodedText" />
<TextView <TextView
android:id="@+id/tt_item_name_nodesc" android:id="@+id/tt_item_name_nodesc"
@ -47,13 +51,14 @@
android:fontFamily="@font/montserrat_medium" android:fontFamily="@font/montserrat_medium"
android:visibility="gone" android:visibility="gone"
android:layout_marginTop="2.5dp" android:layout_marginTop="2.5dp"
android:layout_toLeftOf="@id/tt_item_room" android:layout_toStartOf="@id/tt_item_room"
android:text="Óra neve" android:text="Óra neve"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_toRightOf="@id/tt_item_num" android:layout_toEndOf="@id/tt_item_num"
android:textColor="@color/text"></TextView> android:textColor="@color/text"
tools:ignore="HardcodedText" />
<TextView <TextView
android:id="@+id/tt_item_desc" android:id="@+id/tt_item_desc"
@ -67,10 +72,11 @@
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_toRightOf="@id/tt_item_num" android:layout_toEndOf="@id/tt_item_num"
android:layout_toLeftOf="@id/tt_item_room" android:layout_toStartOf="@id/tt_item_room"
android:layout_below="@id/tt_item_name" android:layout_below="@id/tt_item_name"
android:textColor="@color/text_desc"></TextView> android:textColor="@color/text_desc"
tools:ignore="HardcodedText" />
<TextView <TextView
android:id="@+id/tt_item_room" android:id="@+id/tt_item_room"
@ -84,8 +90,9 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2" android:maxLines="2"
android:gravity="center" android:gravity="center"
android:layout_toLeftOf="@id/tt_item_time" android:layout_toStartOf="@id/tt_item_time"
android:textColor="@color/text_desc"></TextView> android:textColor="@color/text_desc"
tools:ignore="HardcodedText" />
<TextView <TextView
android:id="@+id/tt_item_time" android:id="@+id/tt_item_time"
@ -95,11 +102,12 @@
android:textFontWeight="500" android:textFontWeight="500"
android:fontFamily="@font/montserrat_medium" android:fontFamily="@font/montserrat_medium"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginLeft="2dp" android:layout_marginStart="2dp"
android:layout_marginTop="-2dp" android:layout_marginTop="-2dp"
android:text="8:30\n9:10" android:text="8:30\n9:10"
android:gravity="center" android:gravity="center"
android:layout_alignParentRight="true" android:layout_alignParentEnd="true"
android:textColor="@color/white"></TextView> android:textColor="@color/white"
tools:ignore="HardcodedText" />
</RelativeLayout> </RelativeLayout>

View File

@ -19,10 +19,12 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:text="Empty" android:text="Üres / Empty"
android:background="@drawable/widget_card_bottom_dark"
android:textColor="@color/text" android:textColor="@color/text"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold"
tools:ignore="HardcodedText" />
<ListView <ListView
android:id="@+id/widget_list" android:id="@+id/widget_list"
@ -43,57 +45,63 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="45dp" android:layout_height="45dp"
android:background="@drawable/widget_card_top_dark"> android:background="@drawable/widget_card_top_dark">
<ImageView <ImageView
android:id="@+id/nav_refresh" android:id="@+id/nav_refresh"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_toStartOf="@id/nav_to_left"
android:clickable="true" android:clickable="true"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:padding="10dp"
android:src="@drawable/ic_refresh_cw" android:src="@drawable/ic_refresh_cw"
android:layout_toLeftOf="@id/nav_to_left"
android:tint="@color/text_desc" android:tint="@color/text_desc"
tools:ignore="UseAppTint" tools:ignore="UseAppTint"
android:padding="10dp" /> android:focusable="true" />
<ImageView <ImageView
android:id="@+id/nav_to_left" android:id="@+id/nav_to_left"
android:layout_width="50dp" android:layout_width="45dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_toStartOf="@id/nav_to_right"
android:clickable="true" android:clickable="true"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:padding="10dp"
android:src="@drawable/ic_chevron_left" android:src="@drawable/ic_chevron_left"
android:layout_toLeftOf="@id/nav_to_right"
android:tint="@color/text_desc" android:tint="@color/text_desc"
tools:ignore="UseAppTint" tools:ignore="UseAppTint"
android:padding="10dp" /> android:focusable="true" />
<ImageView <ImageView
android:id="@+id/nav_to_right" android:id="@+id/nav_to_right"
android:layout_width="50dp" android:layout_width="45dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_marginEnd="5dp"
android:clickable="true" android:clickable="true"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:padding="10dp"
android:src="@drawable/ic_chevron_right" android:src="@drawable/ic_chevron_right"
android:layout_alignParentRight="true"
android:layout_marginRight="5dp"
android:tint="@color/text_desc" android:tint="@color/text_desc"
tools:ignore="UseAppTint" tools:ignore="UseAppTint"
android:padding="10dp" /> android:focusable="true" />
<TextView <TextView
android:id="@+id/nav_current" android:id="@+id/nav_current"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentLeft="true" android:layout_alignParentStart="true"
android:layout_marginLeft="20sp" android:layout_marginStart="15sp"
android:layout_toLeftOf="@id/nav_refresh" android:layout_marginTop="2sp"
android:layout_toStartOf="@id/nav_refresh"
android:fontFamily="@font/montserrat_medium" android:fontFamily="@font/montserrat_medium"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="1" android:maxLines="1"
android:text="Timetable" android:text="Órarend"
android:textColor="@color/text" android:textColor="@color/text"
android:textSize="24sp" android:textSize="22sp"
android:textStyle="bold" /> android:textStyle="bold"
tools:ignore="HardcodedText" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>
@ -131,8 +139,9 @@
android:text="A widget használatához, bejelentkezés szükséges." android:text="A widget használatához, bejelentkezés szükséges."
android:textColor="@color/black" android:textColor="@color/black"
android:paddingTop="10dp" android:paddingTop="10dp"
android:textSize="17dp" android:textSize="17sp"
android:textStyle="bold" /> android:textStyle="bold"
tools:ignore="HardcodedText" />
<Button <Button
android:id="@+id/open_login" android:id="@+id/open_login"
@ -145,8 +154,9 @@
android:layout_margin="10dp" android:layout_margin="10dp"
android:textColor="@color/white" android:textColor="@color/white"
android:fontFamily="@font/montserrat_medium" android:fontFamily="@font/montserrat_medium"
android:textSize="16dp" android:textSize="16sp"
android:textStyle="bold"/> android:textStyle="bold"
tools:ignore="HardcodedText" />
</RelativeLayout> </RelativeLayout>
@ -185,8 +195,9 @@
android:text="Órák a kezdőképernyőd kényelméből." android:text="Órák a kezdőképernyőd kényelméből."
android:textColor="@color/black" android:textColor="@color/black"
android:paddingTop="10dp" android:paddingTop="10dp"
android:textSize="16dp" android:textSize="16sp"
android:textStyle="bold" /> android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -197,7 +208,8 @@
android:textColor="@color/black" android:textColor="@color/black"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:layout_marginHorizontal="15dp" android:layout_marginHorizontal="15dp"
android:textSize="14dp" /> android:textSize="14sp"
tools:ignore="HardcodedText" />
<Button <Button
android:id="@+id/buy_premium" android:id="@+id/buy_premium"
@ -211,8 +223,9 @@
android:layout_margin="10dp" android:layout_margin="10dp"
android:textColor="#ff691A9B" android:textColor="#ff691A9B"
android:fontFamily="@font/montserrat_medium" android:fontFamily="@font/montserrat_medium"
android:textSize="16dp" android:textSize="16sp"
android:textStyle="bold"/> android:textStyle="bold"
tools:ignore="HardcodedText" />
</RelativeLayout> </RelativeLayout>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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>

View File

@ -27,7 +27,7 @@
<color name="yellow_light">#ffFFCC00</color> <color name="yellow_light">#ffFFCC00</color>
<color name="light_yellow_light">#40FFD60A</color> <color name="light_yellow_light">#40FFD60A</color>
<color name="green_light">#ff34C759</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="teal_light">#ff5AC8FA</color>
<color name="blue_light">#ff007AFF</color> <color name="blue_light">#ff007AFF</color>
<color name="indigo_light">#ff5856D6</color> <color name="indigo_light">#ff5856D6</color>
@ -49,7 +49,7 @@
<color name="yellow">#ffFFD60A</color> <color name="yellow">#ffFFD60A</color>
<color name="light_yellow">#40FFD60A</color> <color name="light_yellow">#40FFD60A</color>
<color name="green">#ff32D74B</color> <color name="green">#ff32D74B</color>
<color name="filc">#ff29826F</color> <color name="filc">#ff3D7BF4</color>
<color name="teal">#ff64D2FF</color> <color name="teal">#ff64D2FF</color>
<color name="blue">#ff0A84FF</color> <color name="blue">#ff0A84FF</color>
<color name="indigo">#ff5E5CE6</color> <color name="indigo">#ff5E5CE6</color>

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -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: ce3938a0df3cc1ef404671531facef740d03f920
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: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
quick_actions_ios: 9e80dcfadfbc5d47d9cf8f47bcf428b11cf383d4
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
SDWebImageWebPCoder: 18503de6621dd2c420d680e33d46bf8e1d5169b0
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
PODFILE CHECKSUM: efe5e2e257c61a1c3d5be693ef1a8ea1c4b81d8f
COCOAPODS: 1.12.1

View File

@ -198,9 +198,9 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 3127F79F28EAEDE300C2EFB3 /* Build configuration list for PBXNativeTarget "livecard" */; buildConfigurationList = 3127F79F28EAEDE300C2EFB3 /* Build configuration list for PBXNativeTarget "livecard" */;
buildPhases = ( buildPhases = (
3127F78A28EAEDE200C2EFB3 /* Resources */,
3127F78828EAEDE200C2EFB3 /* Sources */, 3127F78828EAEDE200C2EFB3 /* Sources */,
3127F78928EAEDE200C2EFB3 /* Frameworks */, 3127F78928EAEDE200C2EFB3 /* Frameworks */,
3127F78A28EAEDE200C2EFB3 /* Resources */,
); );
buildRules = ( buildRules = (
); );
@ -299,7 +299,7 @@
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 12;
files = ( files = (
); );
inputPaths = ( inputPaths = (
@ -310,11 +310,11 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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 */ = { 71459C0EB905E05018E3D78F /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 12;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
@ -364,7 +364,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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 */ /* End PBXShellScriptBuildPhase section */

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.refilcnaplo.livecard</string> <string>group.refilcnaplo.livecard</string>

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
class LessonData { class LessonData {
var color: String
var icon: String var icon: String
var index: String var index: String
var title: String var title: String
@ -15,6 +16,7 @@ class LessonData {
init?() { init?() {
let sharedDefault = UserDefaults(suiteName: "group.refilc.livecard")! let sharedDefault = UserDefaults(suiteName: "group.refilc.livecard")!
self.color = sharedDefault.string(forKey: "color")!
self.icon = sharedDefault.string(forKey: "icon")! self.icon = sharedDefault.string(forKey: "icon")!
self.index = sharedDefault.string(forKey: "index")! self.index = sharedDefault.string(forKey: "index")!
self.title = sharedDefault.string(forKey: "title")! self.title = sharedDefault.string(forKey: "title")!

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.refilcnaplo.livecard</string> <string>group.refilcnaplo.livecard</string>

View File

@ -11,6 +11,33 @@ struct Widgets: WidgetBundle {
} }
} }
// Color Converter
extension Color {
init(hex: String, alpha: Double = 1.0) {
var hexValue = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if hexValue.hasPrefix("#") {
hexValue.remove(at: hexValue.startIndex)
}
var rgbValue: UInt64 = 0
Scanner(string: hexValue).scanHexInt64(&rgbValue)
let red = Double((rgbValue & 0xFF0000) >> 16) / 255.0
let green = Double((rgbValue & 0x00FF00) >> 8) / 255.0
let blue = Double(rgbValue & 0x0000FF) / 255.0
self.init(
.sRGB,
red: red,
green: green,
blue: blue,
opacity: alpha
)
}
}
// We need to redefined live activities pipe // We need to redefined live activities pipe
struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable { struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
public struct ContentState: Codable, Hashable { } public struct ContentState: Codable, Hashable { }
@ -68,6 +95,11 @@ struct LockScreenLiveActivityView: View {
.monospacedDigit() .monospacedDigit()
.padding(.trailing, CGFloat(24)) .padding(.trailing, CGFloat(24))
} }
.activityBackgroundTint(
lesson!.color == "#676767"
? nil
: Color(hex: lesson!.color)
)
} }
} }
@ -144,7 +176,11 @@ struct LiveCardWidget: Widget {
.font(.system(size: CGFloat(10))) .font(.system(size: CGFloat(10)))
} }
} }
.keylineTint(.accentColor) .keylineTint(
lesson!.color == "#676767"
? nil
: Color(hex: lesson!.color)
)
} }
} }
} }

View File

@ -47,27 +47,28 @@ class LiveCardProvider extends ChangeNotifier {
required SettingsProvider settings, required SettingsProvider settings,
}) : _timetable = timetable, }) : _timetable = timetable,
_settings = settings { _settings = settings {
// Check if live card is enabled .areActivitiesEnabled() if (Platform.isIOS) {
_liveActivitiesPlugin.areActivitiesEnabled().then((value) { _liveActivitiesPlugin.areActivitiesEnabled().then((value) {
// Console log // Console log
if (kDebugMode) { if (kDebugMode) {
print("Live card enabled: $value"); print("Live card enabled: $value");
} }
if (value) { if (value) {
_liveActivitiesPlugin.init(appGroupId: "group.refilc.livecard"); _liveActivitiesPlugin.init(appGroupId: "group.refilc.livecard");
_liveActivitiesPlugin.getAllActivitiesIds().then((value) { _liveActivitiesPlugin.getAllActivitiesIds().then((value) {
_latestActivityId = value.isNotEmpty ? value.first : null; _latestActivityId = value.isNotEmpty ? value.first : null;
}); });
} }
}); });
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => update()); _timer = Timer.periodic(const Duration(seconds: 1), (timer) => update());
_delay = settings.bellDelayEnabled _delay = settings.bellDelayEnabled
? Duration(seconds: settings.bellDelay) ? Duration(seconds: settings.bellDelay)
: Duration.zero; : Duration.zero;
update(); update();
}
} }
@override @override
@ -87,6 +88,7 @@ class LiveCardProvider extends ChangeNotifier {
// Debugging // Debugging
static DateTime _now() { static DateTime _now() {
// return DateTime(2023, 8, 31, 8, 0);
return DateTime.now(); return DateTime.now();
} }
@ -110,6 +112,8 @@ class LiveCardProvider extends ChangeNotifier {
switch (currentState) { switch (currentState) {
case LiveCardState.duringLesson: case LiveCardState.duringLesson:
return { return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": currentLesson != null "icon": currentLesson != null
? SubjectIcon.resolveName(subject: currentLesson?.subject) ? SubjectIcon.resolveName(subject: currentLesson?.subject)
: "book", : "book",
@ -142,6 +146,8 @@ class LiveCardProvider extends ChangeNotifier {
final diff = getFloorDifference(); final diff = getFloorDifference();
return { return {
"color":
'#${_settings.liveActivityColor.toString().substring(10, 16)}',
"icon": iconFloorMap[diff] ?? "cup.and.saucer", "icon": iconFloorMap[diff] ?? "cup.and.saucer",
"title": "Szünet", "title": "Szünet",
"description": "go $diff".i18n.fill([ "description": "go $diff".i18n.fill([

View File

@ -1,7 +1,4 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:math';
import 'package:filcnaplo/api/client.dart'; import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/news.dart'; import 'package:filcnaplo/models/news.dart';
import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/models/settings.dart';
@ -11,7 +8,7 @@ import 'package:provider/provider.dart';
class NewsProvider extends ChangeNotifier { class NewsProvider extends ChangeNotifier {
// Private // Private
late List<News> _news; late List<News> _news;
late int _state; //late int _state;
late int _fresh; late int _fresh;
bool show = false; bool show = false;
late BuildContext _context; late BuildContext _context;
@ -30,56 +27,83 @@ class NewsProvider extends ChangeNotifier {
Future<void> restore() async { Future<void> restore() async {
// Load news state from the database // 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(); var news_ = await FilcAPI.getNews();
if (news_ != null) { if (news_ != null) {
state_ = news_.length;
_news = news_; _news = news_;
show = true;
} }
} }
_state = state_; //_state = seen_;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state); // Provider.of<SettingsProvider>(_context, listen: false)
// .update(seenNewsId: news_.id);
} }
Future<void> fetch() async { Future<void> fetch() async {
var news_ = await FilcAPI.getNews(); var news_ = await FilcAPI.getNews();
if (news_ == null) return; if (news_ == null) return;
show = false;
_news = news_; _news = news_;
_fresh = news_.length - _state;
if (_fresh < 0) { for (var news in news_) {
_state = news_.length; if (news.expireDate.isAfter(DateTime.now()) &&
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state); 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);
_fresh = max(_fresh, 0); // _news = news_;
// _fresh = news_.length - _state;
if (_fresh > 0) { // if (_fresh < 0) {
show = true; // _state = news_.length;
notifyListeners(); // 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 lock() => show = false;
void release() { void release() {
if (_fresh == 0) return; // if (_fresh == 0) return;
_fresh--; // _fresh--;
_state++; // //_state++;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state); // // Provider.of<SettingsProvider>(_context, listen: false)
// // .update(seenNewsId: _state);
if (_fresh > 0) { // if (_fresh > 0) {
show = true; // show = true;
} else { // } else {
show = false; // show = false;
} // }
notifyListeners(); // notifyListeners();
} }
} }

View File

@ -12,6 +12,7 @@ import 'package:filcnaplo/theme/observer.dart';
import 'package:filcnaplo/theme/theme.dart'; import 'package:filcnaplo/theme/theme.dart';
import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_premium/providers/goal_provider.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -89,6 +90,7 @@ class App extends StatelessWidget {
return MultiProvider( return MultiProvider(
providers: [ providers: [
// refilc providers
ChangeNotifierProvider<PremiumProvider>(create: (_) => premium), ChangeNotifierProvider<PremiumProvider>(create: (_) => premium),
ChangeNotifierProvider<SettingsProvider>(create: (_) => settings), ChangeNotifierProvider<SettingsProvider>(create: (_) => settings),
ChangeNotifierProvider<UserProvider>(create: (_) => user), ChangeNotifierProvider<UserProvider>(create: (_) => user),
@ -103,7 +105,7 @@ class App extends StatelessWidget {
ChangeNotifierProvider<UpdateProvider>( ChangeNotifierProvider<UpdateProvider>(
create: (context) => UpdateProvider(context: context)), create: (context) => UpdateProvider(context: context)),
// User data providers // user data (kreten) providers
ChangeNotifierProvider<GradeProvider>( ChangeNotifierProvider<GradeProvider>(
create: (_) => GradeProvider( create: (_) => GradeProvider(
settings: settings, settings: settings,
@ -114,7 +116,8 @@ class App extends StatelessWidget {
ChangeNotifierProvider<ExamProvider>( ChangeNotifierProvider<ExamProvider>(
create: (context) => ExamProvider(context: context)), create: (context) => ExamProvider(context: context)),
ChangeNotifierProvider<HomeworkProvider>( ChangeNotifierProvider<HomeworkProvider>(
create: (context) => HomeworkProvider(context: context)), create: (context) =>
HomeworkProvider(context: context, database: database)),
ChangeNotifierProvider<MessageProvider>( ChangeNotifierProvider<MessageProvider>(
create: (context) => MessageProvider(context: context)), create: (context) => MessageProvider(context: context)),
ChangeNotifierProvider<NoteProvider>( ChangeNotifierProvider<NoteProvider>(
@ -124,6 +127,7 @@ class App extends StatelessWidget {
ChangeNotifierProvider<AbsenceProvider>( ChangeNotifierProvider<AbsenceProvider>(
create: (context) => AbsenceProvider(context: context)), create: (context) => AbsenceProvider(context: context)),
// other providers
ChangeNotifierProvider<GradeCalculatorProvider>( ChangeNotifierProvider<GradeCalculatorProvider>(
create: (_) => GradeCalculatorProvider( create: (_) => GradeCalculatorProvider(
settings: settings, settings: settings,
@ -132,7 +136,9 @@ class App extends StatelessWidget {
kreta: kreta)), kreta: kreta)),
ChangeNotifierProvider<LiveCardProvider>( ChangeNotifierProvider<LiveCardProvider>(
create: (context) => create: (context) =>
LiveCardProvider(timetable: timetable, settings: settings)) LiveCardProvider(timetable: timetable, settings: settings)),
ChangeNotifierProvider<GoalProvider>(
create: (context) => GoalProvider(database: database, user: user)),
], ],
child: Consumer<ThemeModeObserver>( child: Consumer<ThemeModeObserver>(
builder: (context, themeMode, child) { builder: (context, themeMode, child) {

View File

@ -12,20 +12,27 @@ import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
const settingsDB = DatabaseStruct("settings", { const settingsDB = DatabaseStruct("settings", {
"language": String, "start_page": int, "rounding": int, "theme": int, "language": String, "start_page": int, "rounding": int, "theme": int,
"accent_color": int, "news": int, "news_state": int, "developer_mode": int, "accent_color": int, "news": int, "seen_news": String,
"developer_mode": int,
"update_channel": int, "config": String, "custom_accent_color": int, "update_channel": int, "config": String, "custom_accent_color": int,
"custom_background_color": int, "custom_highlight_color": int, // general "custom_background_color": int, "custom_highlight_color": int, // general
"grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color1": int, "grade_color2": int, "grade_color3": int,
"grade_color4": int, "grade_color5": int, // grade colors "grade_color4": int, "grade_color5": int, // grade colors
"vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int, "vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int,
"notifications": int, "notifications_bitfield": int, "notifications": int, "notifications_bitfield": int,
"notification_poll_interval": int, // notifications "notification_poll_interval": int,
"notifications_grades": int,
"notifications_absences": int,
"notifications_messages": int,
"notifications_lessons": int, // notifications
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "x_filc_id": String, "graph_class_avg": int, "presentation_mode": int,
"bell_delay": int, "bell_delay_enabled": int, "bell_delay": int, "bell_delay_enabled": int,
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "grade_opening_fun": int, "icon_pack": String, "premium_scopes": String,
"premium_token": String, "premium_login": String, "premium_token": String, "premium_login": String,
"last_account_id": String, "renamed_subjects_enabled": int, "last_account_id": String, "renamed_subjects_enabled": int,
"renamed_subjects_italics": 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 // DON'T FORGET TO UPDATE DEFAULT VALUES IN `initDB` MIGRATION OR ELSE PARENTS WILL COMPLAIN ABOUT THEIR CHILDREN MISSING
// YOU'VE BEEN WARNED!!! // YOU'VE BEEN WARNED!!!
@ -40,8 +47,15 @@ const userDataDB = DatabaseStruct("user_data", {
"events": String, "absences": String, "group_averages": String, "events": String, "absences": String, "group_averages": String,
// renamed subjects // non kreta data // renamed subjects // non kreta data
"renamed_subjects": String, "renamed_subjects": String,
// renamed teachers // non kreta data
"renamed_teachers": String,
// "subject_lesson_count": String, // non kreta data // "subject_lesson_count": String, // non kreta data
"last_seen_grade": int, "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) => Future<void> createTable(Database db, DatabaseStruct struct) =>
@ -89,8 +103,15 @@ Future<Database> initDB(DatabaseProvider database) async {
"group_averages": "[]", "group_averages": "[]",
// renamed subjects // non kreta data // renamed subjects // non kreta data
"renamed_subjects": "{}", "renamed_subjects": "{}",
// renamed teachers // non kreta data
"renamed_teachers": "{}",
// "subject_lesson_count": "{}", // non kreta data // "subject_lesson_count": "{}", // non kreta data
"last_seen_grade": 0, "last_seen_grade": 0,
// goal planning // non kreta data
"goal_plans": "{}",
"goal_averages": "{}",
"goal_befores": "{}",
"goal_pin_dates": "{}",
}); });
} catch (error) { } catch (error) {
print("ERROR: migrateDB: $error"); print("ERROR: migrateDB: $error");

View File

@ -26,7 +26,8 @@ class DatabaseQuery {
Future<SettingsProvider> getSettings(DatabaseProvider database) async { Future<SettingsProvider> getSettings(DatabaseProvider database) async {
Map settingsMap = (await db.query("settings")).elementAt(0); Map settingsMap = (await db.query("settings")).elementAt(0);
SettingsProvider settings = SettingsProvider.fromMap(settingsMap, database: database); SettingsProvider settings =
SettingsProvider.fromMap(settingsMap, database: database);
return settings; return settings;
} }
@ -36,7 +37,10 @@ class DatabaseQuery {
for (var user in usersMap) { for (var user in usersMap) {
userProvider.addUser(User.fromMap(user)); 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); userProvider.setUser(settings.lastAccountId);
} else { } else {
if (usersMap.isNotEmpty) { if (usersMap.isNotEmpty) {
@ -54,100 +58,133 @@ class UserDatabaseQuery {
final Database db; final Database db;
Future<List<Grade>> getGrades({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? gradesJson = userData.elementAt(0)["grades"] as String?; String? gradesJson = userData.elementAt(0)["grades"] as String?;
if (gradesJson == null) return []; 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; return grades;
} }
Future<Map<Week, List<Lesson>>> getLessons({required String userId}) async { 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 {}; if (userData.isEmpty) return {};
String? lessonsJson = userData.elementAt(0)["timetable"] as String?; String? lessonsJson = userData.elementAt(0)["timetable"] as String?;
if (lessonsJson == null) return {}; if (lessonsJson == null) return {};
if (jsonDecode(lessonsJson) is List) return {}; if (jsonDecode(lessonsJson) is List) return {};
Map<Week, List<Lesson>> lessons = (jsonDecode(lessonsJson) as Map).cast<String, List>().map((key, value) { Map<Week, List<Lesson>> lessons =
return MapEntry(Week.fromId(int.parse(key)), value.cast<Map<String, Object?>>().map((e) => Lesson.fromJson(e)).toList()); (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(); }).cast();
return lessons; return lessons;
} }
Future<List<Exam>> getExams({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? examsJson = userData.elementAt(0)["exams"] as String?; String? examsJson = userData.elementAt(0)["exams"] as String?;
if (examsJson == null) return []; 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; return exams;
} }
Future<List<Homework>> getHomework({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? homeworkJson = userData.elementAt(0)["homework"] as String?; String? homeworkJson = userData.elementAt(0)["homework"] as String?;
if (homeworkJson == null) return []; 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; return homework;
} }
Future<List<Message>> getMessages({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? messagesJson = userData.elementAt(0)["messages"] as String?; String? messagesJson = userData.elementAt(0)["messages"] as String?;
if (messagesJson == null) return []; 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; return messages;
} }
Future<List<Note>> getNotes({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? notesJson = userData.elementAt(0)["notes"] as String?; String? notesJson = userData.elementAt(0)["notes"] as String?;
if (notesJson == null) return []; 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; return notes;
} }
Future<List<Event>> getEvents({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? eventsJson = userData.elementAt(0)["events"] as String?; String? eventsJson = userData.elementAt(0)["events"] as String?;
if (eventsJson == null) return []; 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; return events;
} }
Future<List<Absence>> getAbsences({required String userId}) async { 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 []; if (userData.isEmpty) return [];
String? absencesJson = userData.elementAt(0)["absences"] as String?; String? absencesJson = userData.elementAt(0)["absences"] as String?;
if (absencesJson == null) return []; 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; return absences;
} }
Future<List<GroupAverage>> getGroupAverages({required String userId}) async { 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 []; 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 []; 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; return groupAverages;
} }
Future<SubjectLessonCount> getSubjectLessonCount({required String userId}) async { Future<SubjectLessonCount> getSubjectLessonCount(
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); {required String userId}) async {
List<Map> userData =
await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return SubjectLessonCount.fromMap({}); 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({}); if (lessonCountJson == null) return SubjectLessonCount.fromMap({});
SubjectLessonCount lessonCount = SubjectLessonCount.fromMap(jsonDecode(lessonCountJson) as Map); SubjectLessonCount lessonCount =
SubjectLessonCount.fromMap(jsonDecode(lessonCountJson) as Map);
return lessonCount; return lessonCount;
} }
Future<DateTime> lastSeenGrade({required String userId}) async { 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); if (userData.isEmpty) return DateTime(0);
int? lastSeenDate = userData.elementAt(0)["last_seen_grade"] as int?; int? lastSeenDate = userData.elementAt(0)["last_seen_grade"] as int?;
if (lastSeenDate == null) return DateTime(0); if (lastSeenDate == null) return DateTime(0);
@ -155,11 +192,71 @@ class UserDatabaseQuery {
return lastSeen; return lastSeen;
} }
// renamed things
Future<Map<String, String>> renamedSubjects({required String userId}) async { 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 {}; 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 {}; 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()));
} }
} }

View File

@ -27,9 +27,11 @@ class DatabaseStore {
} }
Future<void> storeUser(User user) async { 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) { 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 { } else {
await db.insert("users", user.toMap()); await db.insert("users", user.toMap());
await db.insert("user_data", {"id": user.id}); await db.insert("user_data", {"id": user.id});
@ -49,64 +51,123 @@ class UserDatabaseStore {
Future<void> storeGrades(List<Grade> grades, {required String userId}) async { Future<void> storeGrades(List<Grade> grades, {required String userId}) async {
String gradesJson = jsonEncode(grades.map((e) => e.json).toList()); 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?>>>( 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); 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 { Future<void> storeExams(List<Exam> exams, {required String userId}) async {
String examsJson = jsonEncode(exams.map((e) => e.json).toList()); 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()); 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()); 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 { Future<void> storeNotes(List<Note> notes, {required String userId}) async {
String notesJson = jsonEncode(notes.map((e) => e.json).toList()); 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 { Future<void> storeEvents(List<Event> events, {required String userId}) async {
String eventsJson = jsonEncode(events.map((e) => e.json).toList()); 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()); 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 { Future<void> storeGroupAverages(List<GroupAverage> groupAverages,
String groupAveragesJson = jsonEncode(groupAverages.map((e) => e.json).toList()); {required String userId}) async {
await db.update("user_data", {"group_averages": groupAveragesJson}, where: "id = ?", whereArgs: [userId]); 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()); 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; 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); 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]);
} }
} }

View File

@ -1,76 +1,601 @@
import 'dart:math';
import 'dart:ui';
import 'package:filcnaplo/api/providers/database_provider.dart'; import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart'; import 'package:filcnaplo/api/providers/status_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart'; import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/helpers/notification_helper.i18n.dart'; import 'package:filcnaplo/helpers/notification_helper.i18n.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/absence.dart';
import 'package:filcnaplo_kreta_api/models/grade.dart'; import 'package:filcnaplo_kreta_api/models/grade.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
hide Message;
import 'package:i18n_extension/i18n_widget.dart';
import 'package:intl/intl.dart';
import 'package:filcnaplo_kreta_api/models/message.dart';
class NotificationsHelper { class NotificationsHelper {
late DatabaseProvider database;
late SettingsProvider settingsProvider;
late UserProvider userProvider;
late KretaClient kretaClient;
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
List<T> combineLists<T, K>(
List<T> list1,
List<T> list2,
K Function(T) keyExtractor,
) {
Set<K> uniqueKeys = <K>{};
List<T> combinedList = [];
for (T item in list1) {
K key = keyExtractor(item);
if (!uniqueKeys.contains(key)) {
uniqueKeys.add(key);
combinedList.add(item);
}
}
for (T item in list2) {
K key = keyExtractor(item);
if (!uniqueKeys.contains(key)) {
uniqueKeys.add(key);
combinedList.add(item);
}
}
return combinedList;
}
String dayTitle(DateTime date) {
try {
return DateFormat("EEEE", I18n.locale.languageCode).format(date);
} catch (e) {
return "Unknown";
}
}
@pragma('vm:entry-point') @pragma('vm:entry-point')
void backgroundJob() async { void backgroundJob() async {
// initialize providers // initialize providers
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = database = DatabaseProvider();
FlutterLocalNotificationsPlugin();
DatabaseProvider database = DatabaseProvider();
await database.init(); await database.init();
SettingsProvider settingsProvider = settingsProvider = await database.query.getSettings(database);
await database.query.getSettings(database); userProvider = await database.query.getUsers(settingsProvider);
UserProvider userProvider = await database.query.getUsers(settingsProvider);
if (userProvider.id != null && settingsProvider.notificationsEnabled) { if (userProvider.id != null && settingsProvider.notificationsEnabled) {
// refresh grades // refresh kreta login
final status = StatusProvider(); final status = StatusProvider();
final kretaClient = KretaClient( kretaClient = KretaClient(
user: userProvider, settings: settingsProvider, status: status); user: userProvider, settings: settingsProvider, status: status);
kretaClient.refreshLogin(); kretaClient.refreshLogin();
GradeProvider gradeProvider = GradeProvider( if (settingsProvider.notificationsGradesEnabled) gradeNotification();
settings: settingsProvider, if (settingsProvider.notificationsAbsencesEnabled) absenceNotification();
user: userProvider, if (settingsProvider.notificationsMessagesEnabled) messageNotification();
database: database, if (settingsProvider.notificationsLessonsEnabled) lessonNotification();
kreta: kretaClient); }
gradeProvider.fetch(); }
List<Grade> grades =
await database.userQuery.getGrades(userId: userProvider.id ?? "");
DateTime lastSeenGrade =
await database.userQuery.lastSeenGrade(userId: userProvider.id ?? "");
// loop through grades and see which hasn't been seen yet void gradeNotification() async {
for (Grade grade in grades) { // fetch grades
GradeProvider gradeProvider = GradeProvider(
settings: settingsProvider,
user: userProvider,
database: database,
kreta: kretaClient);
gradeProvider.fetch();
List<Grade> grades =
await database.userQuery.getGrades(userId: userProvider.id ?? "");
DateTime lastSeenGrade =
await database.userQuery.lastSeenGrade(userId: userProvider.id ?? "");
// 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 the grade was added over a week ago, don't show it to avoid notification spam
if (grade.seenDate.isAfter(lastSeenGrade) && if (grade.seenDate.isAfter(lastSeenGrade) &&
grade.date.difference(DateTime.now()).inDays * -1 < 7) { grade.date.difference(DateTime.now()).inDays * -1 < 7) {
// send notificiation about new grade // send notificiation about new grade
const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails('GRADES', 'Jegyek', AndroidNotificationDetails(
channelDescription: 'Értesítés jegyek beírásakor', 'GRADES',
importance: Importance.max, 'Jegyek',
priority: Priority.max, channelDescription: 'Értesítés jegyek beírásakor',
color: Color(0xFF3D7BF4), importance: Importance.max,
ticker: 'Jegyek'); priority: Priority.max,
const NotificationDetails notificationDetails = color: settingsProvider.customAccentColor,
ticker: 'Jegyek',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails); NotificationDetails(android: androidNotificationDetails);
await flutterLocalNotificationsPlugin.show( if (userProvider.getUsers().length == 1) {
// probably shouldn't use a random int await flutterLocalNotificationsPlugin.show(
Random().nextInt(432234 * 2), grade.id.hashCode,
"title".i18n, "title_grade".i18n,
"body".i18n.fill([ "body_grade".i18n.fill(
grade.value.value.toString(), [
grade.subject.isRenamed && grade.value.value.toString(),
settingsProvider.renamedSubjectsEnabled grade.subject.isRenamed &&
? grade.subject.renamedTo! settingsProvider.renamedSubjectsEnabled
: grade.subject.name ? grade.subject.renamedTo!
]), : grade.subject.name
notificationDetails); ],
),
notificationDetails,
);
} else {
// multiple users are added, also display student name
await flutterLocalNotificationsPlugin.show(
grade.id.hashCode,
"title_grade".i18n,
"body_grade_multiuser".i18n.fill(
[
userProvider.displayName!,
grade.value.value.toString(),
grade.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? grade.subject.renamedTo!
: grade.subject.name
],
),
notificationDetails,
);
}
} }
} }
// set grade seen status }
gradeProvider.seenAll(); // set grade seen status
gradeProvider.seenAll();
}
void absenceNotification() async {
// get absences from api
List? absenceJson = await kretaClient
.getAPI(KretaAPI.absences(userProvider.instituteCode ?? ""));
List<Absence> storedAbsences =
await database.userQuery.getAbsences(userId: userProvider.id!);
if (absenceJson == null) {
return;
}
// format api absences to correct format while preserving isSeen value
List<Absence> absences = absenceJson.map((e) {
Absence apiAbsence = Absence.fromJson(e);
Absence storedAbsence = storedAbsences.firstWhere(
(stored) => stored.id == apiAbsence.id,
orElse: () => apiAbsence);
apiAbsence.isSeen = storedAbsence.isSeen;
return apiAbsence;
}).toList();
List<Absence> modifiedAbsences = [];
if (absences != storedAbsences) {
// remove absences that are not new
absences.removeWhere((element) => storedAbsences.contains(element));
for (Absence absence in absences) {
if (!absence.isSeen) {
absence.isSeen = true;
modifiedAbsences.add(absence);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'ABSENCES',
'Hiányzások',
channelDescription: 'Értesítés hiányzások beírásakor',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Hiányzások',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
await flutterLocalNotificationsPlugin.show(
absence.id.hashCode,
"title_absence".i18n,
"body_absence".i18n.fill(
[
DateFormat("yyyy-MM-dd").format(absence.date),
absence.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? absence.subject.renamedTo!
: absence.subject.name
],
),
notificationDetails,
);
} else {
await flutterLocalNotificationsPlugin.show(
absence.id.hashCode,
"title_absence".i18n,
"body_absence_multiuser".i18n.fill(
[
userProvider.displayName!,
DateFormat("yyyy-MM-dd").format(absence.date),
absence.subject.isRenamed &&
settingsProvider.renamedSubjectsEnabled
? absence.subject.renamedTo!
: absence.subject.name
],
),
notificationDetails,
);
}
}
}
}
// combine modified absences and storedabsences list and save them to the database
List<Absence> combinedAbsences = combineLists(
modifiedAbsences,
storedAbsences,
(Absence absence) => absence.id,
);
await database.userStore
.storeAbsences(combinedAbsences, userId: userProvider.id!);
}
void messageNotification() async {
// get messages from api
List? messageJson =
await kretaClient.getAPI(KretaAPI.messages("beerkezett"));
List<Message> storedmessages =
await database.userQuery.getMessages(userId: userProvider.id!);
if (messageJson == null) {
return;
}
// format api messages to correct format while preserving isSeen value
// Parse messages
List<Message> messages = [];
await Future.wait(List.generate(messageJson.length, (index) {
return () async {
Map message = messageJson.cast<Map>()[index];
Map? innerMessageJson = await kretaClient
.getAPI(KretaAPI.message(message["azonosito"].toString()));
if (innerMessageJson != null) {
messages.add(
Message.fromJson(innerMessageJson, forceType: MessageType.inbox));
}
}();
}));
for (Message message in messages) {
for (Message storedMessage in storedmessages) {
if (message.id == storedMessage.id) {
message.isSeen = storedMessage.isSeen;
}
}
}
List<Message> modifiedmessages = [];
if (messages != storedmessages) {
// remove messages that are not new
messages.removeWhere((element) => storedmessages.contains(element));
for (Message message in messages) {
if (!message.isSeen) {
message.isSeen = true;
modifiedmessages.add(message);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'MESSAGES',
'Üzenetek',
channelDescription: 'Értesítés kapott üzenetekkor',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Üzenetek',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
await flutterLocalNotificationsPlugin.show(
message.id.hashCode,
message.author,
message.content.replaceAll(RegExp(r'<[^>]*>'), ''),
notificationDetails,
);
} else {
await flutterLocalNotificationsPlugin.show(
message.id.hashCode,
"(${userProvider.displayName!}) ${message.author}",
message.content.replaceAll(RegExp(r'<[^>]*>'), ''),
notificationDetails,
);
}
}
}
}
// combine modified messages and storedmessages list and save them to the database
List<Message> combinedmessages = combineLists(
modifiedmessages,
storedmessages,
(Message message) => message.id,
);
await database.userStore
.storeMessages(combinedmessages, userId: userProvider.id!);
}
void lessonNotification() async {
// get lesson from api
TimetableProvider timetableProvider = TimetableProvider(
user: userProvider, database: database, kreta: kretaClient);
List<Lesson> storedlessons =
timetableProvider.lessons[Week.current()] ?? [];
List? apilessons = timetableProvider.getWeek(Week.current()) ?? [];
for (Lesson lesson in apilessons) {
for (Lesson storedLesson in storedlessons) {
if (lesson.id == storedLesson.id) {
lesson.isSeen = storedLesson.isSeen;
}
}
}
List<Lesson> modifiedlessons = [];
if (apilessons != storedlessons) {
// remove lessons that are not new
apilessons.removeWhere((element) => storedlessons.contains(element));
for (Lesson lesson in apilessons) {
if (!lesson.isSeen && lesson.isChanged) {
lesson.isSeen = true;
modifiedlessons.add(lesson);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'LESSONS',
'Órák',
channelDescription:
'Értesítés órák elmaradásáról, helyettesítésről',
importance: Importance.max,
priority: Priority.max,
color: settingsProvider.customAccentColor,
ticker: 'Órák',
);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
if (userProvider.getUsers().length == 1) {
if (lesson.status?.name == "Elmaradt") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
}
} else if (lesson.substituteTeacher?.name != "") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name,
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
}
}
} else {
if (lesson.status?.name == "Elmaradt") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
userProvider.displayName!,
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_canceled".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date)
],
),
notificationDetails,
);
break;
}
}
} else if (lesson.substituteTeacher?.name != "") {
switch (I18n.localeStr) {
case "en_en":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
case "hu_hu":
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
userProvider.displayName!,
dayTitle(lesson.date),
lesson.lessonIndex,
lesson.name,
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
default:
{
await flutterLocalNotificationsPlugin.show(
lesson.id.hashCode,
"title_lesson".i18n,
"body_lesson_substituted".i18n.fill(
[
userProvider.displayName!,
lesson.lessonIndex,
lesson.name,
dayTitle(lesson.date),
lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo!
: lesson.substituteTeacher!.name
],
),
notificationDetails,
);
break;
}
}
}
}
}
}
// combine modified lesson and storedlesson list and save them to the database
List<Lesson> combinedlessons = combineLists(
modifiedlessons,
storedlessons,
(Lesson message) => message.id,
);
Map<Week, List<Lesson>> timetableLessons = timetableProvider.lessons;
timetableLessons[Week.current()] = combinedlessons;
await database.userStore
.storeLessons(timetableLessons, userId: userProvider.id!);
} }
} }
} }

View File

@ -4,16 +4,43 @@ extension Localization on String {
static final _t = Translations.byLocale("hu_hu") + static final _t = Translations.byLocale("hu_hu") +
{ {
"en_en": { "en_en": {
"title": "New grade", "title_grade": "New grade",
"body": "You got a %s in %s" "body_grade": "You got a %s in %s",
"body_grade_multiuser": "%s got a %s in %s",
"title_absence": "Absence recorded",
"body_absence": "An absence was recorded on %s for %s",
"body_absence_multiuser": "An absence was recorded for %s on %s for the subject %s",
"title_lesson": "Timetable modified",
"body_lesson_canceled": "Lesson #%s (%s) has been canceled on %s",
"body_lesson_canceled_multiuser": "(%s) Lesson #%s (%s) has been canceled on %s",
"body_lesson_substituted": "Lesson #%s (%s) on %s will be substituted by %s",
"body_lesson_substituted_multiuser": "(%s) Lesson #%s (%s) on %s will be substituted by %s"
}, },
"hu_hu": { "hu_hu": {
"title": "Új jegy", "title_grade": "Új jegy",
"body": "%s-st kaptál %s tantárgyból" "body_grade": "%s-st kaptál %s tantárgyból",
"body_grade_multiuser": "%s tanuló %s-st kapott %s tantárgyból",
"title_absence": "Új hiányzás",
"body_absence": "Új hiányzást kaptál %s napon %s tantárgyból",
"body_absence_multiuser": "%s tanuló új hiányzást kapott %s napon %s tantárgyból",
"title_lesson": "Órarend szerkesztve",
"body_lesson_canceled": "%s-i %s. óra (%s) elmarad",
"body_lesson_canceled_multiuser": "(%s) %s-i %s. óra (%s) elmarad",
"body_lesson_substituted": "%s-i %s. (%s) órát %s helyettesíti",
"body_lesson_substituted_multiuser": "(%s) %s-i %s. (%s) órát %s helyettesíti"
}, },
"de_de": { "de_de": {
"title": "Neue Note", "title_grade": "Neue Note",
"body": "Du hast eine %s in %s" "body_grade": "Du hast eine %s in %s",
"body_grade_multiuser": "%s hast eine %s in %s",
"title_absence": "Abwesenheit aufgezeichnet",
"body_absence": "Auf %s für %s wurde eine Abwesenheit aufgezeichnet",
"body_absence_multiuser": "Für %s wurde am %s für das Thema Mathematik eine Abwesenheit aufgezeichnet",
"title_lesson": "Fahrplan geändert",
"body_lesson_canceled": "Lektion Nr. %s (%s) wurde am %s abgesagt",
"body_lesson_canceled_multiuser": "(%s) Lektion Nr. %s (%s) wurde am %s abgesagt",
"body_lesson_substituted": "Lektion Nr. %s (%s) wird am %s durch %s ersetzt",
"body_lesson_substituted_multiuser": "(%s) Lektion Nr. %s (%s) wird am %s durch %s ersetzt"
}, },
}; };

View File

@ -1,30 +1,36 @@
class News { class News {
String id;
String title; String title;
String content; String content;
String link; String link;
String openLabel; String openLabel;
String platform; String platform;
bool emergency; bool emergency;
DateTime expireDate;
Map? json; Map? json;
News({ News({
required this.id,
required this.title, required this.title,
required this.content, required this.content,
required this.link, required this.link,
required this.openLabel, required this.openLabel,
required this.platform, required this.platform,
required this.emergency, required this.emergency,
required this.expireDate,
this.json, this.json,
}); });
factory News.fromJson(Map json) { factory News.fromJson(Map json) {
return News( return News(
id: json["id"] ?? "",
title: json["title"] ?? "", title: json["title"] ?? "",
content: json["content"] ?? "", content: json["content"] ?? "",
link: json["link"] ?? "", link: json["link"] ?? "",
openLabel: json["open_label"] ?? "", openLabel: json["open_label"] ?? "",
platform: json["platform"] ?? "", platform: json["platform"] ?? "",
emergency: json["emergency"] ?? false, emergency: json["emergency"] ?? false,
expireDate: DateTime.parse(json["expire_date"] ?? ''),
json: json, json: json,
); );
} }

View File

@ -29,8 +29,12 @@ class SettingsProvider extends ChangeNotifier {
// zero is one, ... // zero is one, ...
List<Color> _gradeColors; List<Color> _gradeColors;
bool _newsEnabled; bool _newsEnabled;
int _newsState; String _seenNews;
bool _notificationsEnabled; bool _notificationsEnabled;
bool _notificationsGradesEnabled;
bool _notificationsAbsencesEnabled;
bool _notificationsMessagesEnabled;
bool _notificationsLessonsEnabled;
/* /*
notificationsBitfield values: notificationsBitfield values:
@ -69,6 +73,9 @@ class SettingsProvider extends ChangeNotifier {
String _lastAccountId; String _lastAccountId;
bool _renamedSubjectsEnabled; bool _renamedSubjectsEnabled;
bool _renamedSubjectsItalics; bool _renamedSubjectsItalics;
bool _renamedTeachersEnabled;
bool _renamedTeachersItalics;
Color _liveActivityColor;
SettingsProvider({ SettingsProvider({
DatabaseProvider? database, DatabaseProvider? database,
@ -79,8 +86,12 @@ class SettingsProvider extends ChangeNotifier {
required AccentColor accentColor, required AccentColor accentColor,
required List<Color> gradeColors, required List<Color> gradeColors,
required bool newsEnabled, required bool newsEnabled,
required int newsState, required String seenNews,
required bool notificationsEnabled, required bool notificationsEnabled,
required bool notificationsGradesEnabled,
required bool notificationsAbsencesEnabled,
required bool notificationsMessagesEnabled,
required bool notificationsLessonsEnabled,
required int notificationsBitfield, required int notificationsBitfield,
required bool developerMode, required bool developerMode,
required int notificationPollInterval, required int notificationPollInterval,
@ -106,6 +117,9 @@ class SettingsProvider extends ChangeNotifier {
required String lastAccountId, required String lastAccountId,
required bool renameSubjectsEnabled, required bool renameSubjectsEnabled,
required bool renameSubjectsItalics, required bool renameSubjectsItalics,
required bool renameTeachersEnabled,
required bool renameTeachersItalics,
required Color liveActivityColor,
}) : _database = database, }) : _database = database,
_language = language, _language = language,
_startPage = startPage, _startPage = startPage,
@ -114,8 +128,12 @@ class SettingsProvider extends ChangeNotifier {
_accentColor = accentColor, _accentColor = accentColor,
_gradeColors = gradeColors, _gradeColors = gradeColors,
_newsEnabled = newsEnabled, _newsEnabled = newsEnabled,
_newsState = newsState, _seenNews = seenNews,
_notificationsEnabled = notificationsEnabled, _notificationsEnabled = notificationsEnabled,
_notificationsGradesEnabled = notificationsGradesEnabled,
_notificationsAbsencesEnabled = notificationsAbsencesEnabled,
_notificationsMessagesEnabled = notificationsMessagesEnabled,
_notificationsLessonsEnabled = notificationsLessonsEnabled,
_notificationsBitfield = notificationsBitfield, _notificationsBitfield = notificationsBitfield,
_developerMode = developerMode, _developerMode = developerMode,
_notificationPollInterval = notificationPollInterval, _notificationPollInterval = notificationPollInterval,
@ -140,7 +158,10 @@ class SettingsProvider extends ChangeNotifier {
_premiumLogin = premiumLogin, _premiumLogin = premiumLogin,
_lastAccountId = lastAccountId, _lastAccountId = lastAccountId,
_renamedSubjectsEnabled = renameSubjectsEnabled, _renamedSubjectsEnabled = renameSubjectsEnabled,
_renamedSubjectsItalics = renameSubjectsItalics; _renamedSubjectsItalics = renameSubjectsItalics,
_renamedTeachersEnabled = renameTeachersEnabled,
_renamedTeachersItalics = renameTeachersItalics,
_liveActivityColor = liveActivityColor;
factory SettingsProvider.fromMap(Map map, factory SettingsProvider.fromMap(Map map,
{required DatabaseProvider database}) { {required DatabaseProvider database}) {
@ -167,8 +188,12 @@ class SettingsProvider extends ChangeNotifier {
Color(map["grade_color5"]), Color(map["grade_color5"]),
], ],
newsEnabled: map["news"] == 1, newsEnabled: map["news"] == 1,
newsState: map["news_state"], seenNews: map["seen_news"],
notificationsEnabled: map["notifications"] == 1, notificationsEnabled: map["notifications"] == 1,
notificationsGradesEnabled: map["notifications_grades"] == 1,
notificationsAbsencesEnabled: map["notifications_absences"] == 1,
notificationsMessagesEnabled: map["notifications_messages"] == 1,
notificationsLessonsEnabled: map["notifications_lessons"] == 1,
notificationsBitfield: map["notifications_bitfield"], notificationsBitfield: map["notifications_bitfield"],
notificationPollInterval: map["notification_poll_interval"], notificationPollInterval: map["notification_poll_interval"],
developerMode: map["developer_mode"] == 1, developerMode: map["developer_mode"] == 1,
@ -195,6 +220,9 @@ class SettingsProvider extends ChangeNotifier {
lastAccountId: map["last_account_id"], lastAccountId: map["last_account_id"],
renameSubjectsEnabled: map["renamed_subjects_enabled"] == 1, renameSubjectsEnabled: map["renamed_subjects_enabled"] == 1,
renameSubjectsItalics: map["renamed_subjects_italics"] == 1, 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,8 +234,12 @@ class SettingsProvider extends ChangeNotifier {
"theme": _theme.index, "theme": _theme.index,
"accent_color": _accentColor.index, "accent_color": _accentColor.index,
"news": _newsEnabled ? 1 : 0, "news": _newsEnabled ? 1 : 0,
"news_state": _newsState, "seen_news": _seenNews,
"notifications": _notificationsEnabled ? 1 : 0, "notifications": _notificationsEnabled ? 1 : 0,
"notifications_grades": _notificationsGradesEnabled ? 1 : 0,
"notifications_absences": _notificationsAbsencesEnabled ? 1 : 0,
"notifications_messages": _notificationsMessagesEnabled ? 1 : 0,
"notifications_lessons": _notificationsLessonsEnabled ? 1 : 0,
"notifications_bitfield": _notificationsBitfield, "notifications_bitfield": _notificationsBitfield,
"developer_mode": _developerMode ? 1 : 0, "developer_mode": _developerMode ? 1 : 0,
"grade_color1": _gradeColors[0].value, "grade_color1": _gradeColors[0].value,
@ -236,7 +268,10 @@ class SettingsProvider extends ChangeNotifier {
"premium_login": _premiumLogin, "premium_login": _premiumLogin,
"last_account_id": _lastAccountId, "last_account_id": _lastAccountId,
"renamed_subjects_enabled": _renamedSubjectsEnabled ? 1 : 0, "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,8 +291,12 @@ class SettingsProvider extends ChangeNotifier {
DarkMobileAppColors().gradeFive, DarkMobileAppColors().gradeFive,
], ],
newsEnabled: true, newsEnabled: true,
newsState: -1, seenNews: '',
notificationsEnabled: true, notificationsEnabled: true,
notificationsGradesEnabled: true,
notificationsAbsencesEnabled: true,
notificationsMessagesEnabled: true,
notificationsLessonsEnabled: true,
notificationsBitfield: 255, notificationsBitfield: 255,
developerMode: false, developerMode: false,
notificationPollInterval: 1, notificationPollInterval: 1,
@ -283,6 +322,9 @@ class SettingsProvider extends ChangeNotifier {
lastAccountId: "", lastAccountId: "",
renameSubjectsEnabled: false, renameSubjectsEnabled: false,
renameSubjectsItalics: false, renameSubjectsItalics: false,
renameTeachersEnabled: false,
renameTeachersItalics: false,
liveActivityColor: const Color(0xFF676767),
); );
} }
@ -294,8 +336,12 @@ class SettingsProvider extends ChangeNotifier {
AccentColor get accentColor => _accentColor; AccentColor get accentColor => _accentColor;
List<Color> get gradeColors => _gradeColors; List<Color> get gradeColors => _gradeColors;
bool get newsEnabled => _newsEnabled; bool get newsEnabled => _newsEnabled;
int get newsState => _newsState; List<String> get seenNews => _seenNews.split(',');
bool get notificationsEnabled => _notificationsEnabled; bool get notificationsEnabled => _notificationsEnabled;
bool get notificationsGradesEnabled => _notificationsGradesEnabled;
bool get notificationsAbsencesEnabled => _notificationsAbsencesEnabled;
bool get notificationsMessagesEnabled => _notificationsMessagesEnabled;
bool get notificationsLessonsEnabled => _notificationsLessonsEnabled;
int get notificationsBitfield => _notificationsBitfield; int get notificationsBitfield => _notificationsBitfield;
bool get developerMode => _developerMode; bool get developerMode => _developerMode;
int get notificationPollInterval => _notificationPollInterval; int get notificationPollInterval => _notificationPollInterval;
@ -324,6 +370,9 @@ class SettingsProvider extends ChangeNotifier {
String get lastAccountId => _lastAccountId; String get lastAccountId => _lastAccountId;
bool get renamedSubjectsEnabled => _renamedSubjectsEnabled; bool get renamedSubjectsEnabled => _renamedSubjectsEnabled;
bool get renamedSubjectsItalics => _renamedSubjectsItalics; bool get renamedSubjectsItalics => _renamedSubjectsItalics;
bool get renamedTeachersEnabled => _renamedTeachersEnabled;
bool get renamedTeachersItalics => _renamedTeachersItalics;
Color get liveActivityColor => _liveActivityColor;
Future<void> update({ Future<void> update({
bool store = true, bool store = true,
@ -334,8 +383,12 @@ class SettingsProvider extends ChangeNotifier {
AccentColor? accentColor, AccentColor? accentColor,
List<Color>? gradeColors, List<Color>? gradeColors,
bool? newsEnabled, bool? newsEnabled,
int? newsState, String? seenNewsId,
bool? notificationsEnabled, bool? notificationsEnabled,
bool? notificationsGradesEnabled,
bool? notificationsAbsencesEnabled,
bool? notificationsMessagesEnabled,
bool? notificationsLessonsEnabled,
int? notificationsBitfield, int? notificationsBitfield,
bool? developerMode, bool? developerMode,
int? notificationPollInterval, int? notificationPollInterval,
@ -361,6 +414,9 @@ class SettingsProvider extends ChangeNotifier {
String? lastAccountId, String? lastAccountId,
bool? renamedSubjectsEnabled, bool? renamedSubjectsEnabled,
bool? renamedSubjectsItalics, bool? renamedSubjectsItalics,
bool? renamedTeachersEnabled,
bool? renamedTeachersItalics,
Color? liveActivityColor,
}) async { }) async {
if (language != null && language != _language) _language = language; if (language != null && language != _language) _language = language;
if (startPage != null && startPage != _startPage) _startPage = startPage; if (startPage != null && startPage != _startPage) _startPage = startPage;
@ -375,11 +431,31 @@ class SettingsProvider extends ChangeNotifier {
if (newsEnabled != null && newsEnabled != _newsEnabled) { if (newsEnabled != null && newsEnabled != _newsEnabled) {
_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 && if (notificationsEnabled != null &&
notificationsEnabled != _notificationsEnabled) { notificationsEnabled != _notificationsEnabled) {
_notificationsEnabled = notificationsEnabled; _notificationsEnabled = notificationsEnabled;
} }
if (notificationsGradesEnabled != null &&
notificationsGradesEnabled != _notificationsGradesEnabled) {
_notificationsGradesEnabled = notificationsGradesEnabled;
}
if (notificationsAbsencesEnabled != null &&
notificationsAbsencesEnabled != _notificationsAbsencesEnabled) {
_notificationsAbsencesEnabled = notificationsAbsencesEnabled;
}
if (notificationsMessagesEnabled != null &&
notificationsMessagesEnabled != _notificationsMessagesEnabled) {
_notificationsMessagesEnabled = notificationsMessagesEnabled;
}
if (notificationsLessonsEnabled != null &&
notificationsLessonsEnabled != _notificationsLessonsEnabled) {
_notificationsLessonsEnabled = notificationsLessonsEnabled;
}
if (notificationsBitfield != null && if (notificationsBitfield != null &&
notificationsBitfield != _notificationsBitfield) { notificationsBitfield != _notificationsBitfield) {
_notificationsBitfield = notificationsBitfield; _notificationsBitfield = notificationsBitfield;
@ -448,6 +524,17 @@ class SettingsProvider extends ChangeNotifier {
renamedSubjectsItalics != _renamedSubjectsItalics) { 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); if (store) await _database?.store.storeSettings(this);
notifyListeners(); notifyListeners();
} }

View File

@ -12,7 +12,8 @@ import 'package:filcnaplo_mobile_ui/common/widgets/lesson/changed_lesson_tile.da
import 'package:filcnaplo/utils/format.dart'; import 'package:filcnaplo/utils/format.dart';
// difference.inDays is not reliable // 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( List<Widget> sortDateWidgets(
BuildContext context, { BuildContext context, {
@ -35,13 +36,16 @@ List<Widget> sortDateWidgets(
if (message.conversationId != null) { if (message.conversationId != null) {
convMessages.add(w); 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); conv.add(message);
if (conv.messages.length == 1) conversations.add(conv); if (conv.messages.length == 1) conversations.add(conv);
} }
if (conversations.any((c) => c.id == message.messageId)) { 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); convMessages.add(w);
conv.add(message); conv.add(message);
} }
@ -87,26 +91,41 @@ List<Widget> sortDateWidgets(
// Group Absence Tiles // Group Absence Tiles
List<DateWidget> absenceTileWidgets = elements.where((element) { 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(); }).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) { 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) { if (elements.isEmpty) {
cst = false; cst = false;
} }
elements.add(DateWidget( elements.add(
widget: AbsenceGroupTile(absenceTiles, showDate: !cst), DateWidget(
date: absenceTileWidgets.first.date, widget: AbsenceGroupTile(
key: "${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group")); absenceTiles,
showDate: !cst,
padding: const EdgeInsets.symmetric(horizontal: 6.0),
),
date: absenceTileWidgets.first.date,
key:
"${absenceTileWidgets.first.date.millisecondsSinceEpoch}-absence-group"),
);
} }
// Bring Lesson Tiles to front & sort by index asc // Bring Lesson Tiles to front & sort by index asc
List<DateWidget> lessonTiles = elements.where((element) { List<DateWidget> lessonTiles = elements.where((element) {
return element.widget.runtimeType == ChangedLessonTile; return element.widget.runtimeType == ChangedLessonTile;
}).toList(); }).toList();
lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile).lesson.lessonIndex.compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex)); lessonTiles.sort((a, b) => (a.widget as ChangedLessonTile)
elements.removeWhere((element) => element.widget.runtimeType == ChangedLessonTile); .lesson
.lessonIndex
.compareTo((b.widget as ChangedLessonTile).lesson.lessonIndex));
elements.removeWhere(
(element) => element.widget.runtimeType == ChangedLessonTile);
elements.insertAll(0, lessonTiles); elements.insertAll(0, lessonTiles);
final date = (elements + absenceTileWidgets).first.date; final date = (elements + absenceTileWidgets).first.date;
@ -122,7 +141,8 @@ List<Widget> sortDateWidgets(
spawnIsolate: false, spawnIsolate: false,
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), 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, items: elements,
), ),
), ),
@ -131,9 +151,12 @@ List<Widget> sortDateWidgets(
} }
final nh = DateTime.now(); 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( items.add(
DateWidget( DateWidget(
date: now, date: now,
@ -153,7 +176,9 @@ List<Widget> sortDateWidgets(
} }
// Sort future dates asc, past dates desc // 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(); return items.map((e) => e.widget).toList();
} }

View File

@ -89,7 +89,9 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData,
// Grades // Grades
case FilterType.grades: case FilterType.grades:
gradeProvider.seenAll(); if(!settingsProvider.gradeOpeningFun) {
gradeProvider.seenAll();
}
items = grade_filter.getWidgets( items = grade_filter.getWidgets(
gradeProvider.grades, gradeProvider.lastSeenDate); gradeProvider.grades, gradeProvider.lastSeenDate);
if (settingsProvider.gradeOpeningFun) { if (settingsProvider.gradeOpeningFun) {

View File

@ -192,6 +192,7 @@ class GradeValueWidget extends StatelessWidget {
this.outline = false, this.outline = false,
this.complemented = false, this.complemented = false,
this.nocolor = false, this.nocolor = false,
this.color,
}) : super(key: key); }) : super(key: key);
final GradeValue value; final GradeValue value;
@ -202,6 +203,7 @@ class GradeValueWidget extends StatelessWidget {
final bool outline; final bool outline;
final bool complemented; final bool complemented;
final bool nocolor; final bool nocolor;
final Color? color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -209,7 +211,7 @@ class GradeValueWidget extends StatelessWidget {
bool isSubjectView = SubjectGradesContainer.of(context) != null; bool isSubjectView = SubjectGradesContainer.of(context) != null;
Color color = Color color =
gradeColor(context: context, value: value.value, nocolor: nocolor); this.color ?? gradeColor(context: context, value: value.value, nocolor: nocolor);
Widget valueText; Widget valueText;
final percentage = value.percentage; final percentage = value.percentage;

View File

@ -31,7 +31,7 @@ class LessonTile extends StatelessWidget {
bool fill = false; bool fill = false;
bool fillLeading = false; bool fillLeading = false;
String lessonIndexTrailing = ""; String lessonIndexTrailing = "";
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context); SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
// Only put a trailing . if its a digit // Only put a trailing . if its a digit
@ -44,7 +44,7 @@ class LessonTile extends StatelessWidget {
fillLeading = true; fillLeading = true;
} }
if (lesson.substituteTeacher != "") { if (lesson.substituteTeacher?.name != "") {
fill = true; fill = true;
accent = AppColors.of(context).yellow; accent = AppColors.of(context).yellow;
} }
@ -113,7 +113,7 @@ class LessonTile extends StatelessWidget {
if (lesson.isChanged) { if (lesson.isChanged) {
if (lesson.status?.name == "Elmaradt") { if (lesson.status?.name == "Elmaradt") {
description = 'cancelled'.i18n; description = 'cancelled'.i18n;
} else if (lesson.substituteTeacher != "") { } else if (lesson.substituteTeacher?.name != "") {
description = 'substitution'.i18n; description = 'substitution'.i18n;
} }
} }
@ -161,8 +161,10 @@ class LessonTile extends StatelessWidget {
color: AppColors.of(context) color: AppColors.of(context)
.text .text
.withOpacity(!lesson.isEmpty ? 1.0 : 0.5), .withOpacity(!lesson.isEmpty ? 1.0 : 0.5),
fontStyle: fontStyle: lesson.subject.isRenamed &&
lesson.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
), ),
subtitle: description != "" subtitle: description != ""
? Text( ? Text(

View File

@ -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);
}

View File

@ -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_

View File

@ -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)

View File

@ -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"))
}

View File

@ -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

View File

@ -3,7 +3,7 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez"
homepage: https://refilc.hu homepage: https://refilc.hu
publish_to: "none" publish_to: "none"
version: 4.1.1+216 version: 4.2.0+220
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"
@ -68,6 +68,9 @@ dependencies:
screenshot: ^2.1.0 screenshot: ^2.1.0
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
sqflite_common_ffi_web: ^0.4.0 sqflite_common_ffi_web: ^0.4.0
image_crop:
git:
url: https://github.com/kimaah/image_crop.git
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1
@ -83,6 +86,7 @@ flutter:
- assets/icons/ic_splash.png - assets/icons/ic_splash.png
- assets/animations/ - assets/animations/
- assets/images/ - assets/images/
- assets/images/subject_covers/
fonts: fonts:
- family: FilcIcons - family: FilcIcons

View File

@ -1,30 +1,30 @@
// This is a basic Flutter widget test. // // This is a basic Flutter widget test.
// // //
// To perform an interaction with a widget in your test, use the WidgetTester // // 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 // // 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 // // 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. // // tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; // import 'package:flutter_test/flutter_test.dart';
import 'package:filcnaplo/main.dart'; // import 'package:filcnaplo/main.dart';
void main() { // void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { // testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. // // Build our app and trigger a frame.
await tester.pumpWidget(const MyApp()); // await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0. // // Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget); // expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing); // expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame. // // Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add)); // await tester.tap(find.byIcon(Icons.add));
await tester.pump(); // await tester.pump();
// Verify that our counter has incremented. // // Verify that our counter has incremented.
expect(find.text('0'), findsNothing); // expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget); // expect(find.text('1'), findsOneWidget);
}); // });
} // }

View File

@ -35,7 +35,8 @@ class SubjectAbsence {
List<Absence> absences; List<Absence> absences;
double percentage; 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 { class AbsencesPage extends StatefulWidget {
@ -45,7 +46,8 @@ class AbsencesPage extends StatefulWidget {
_AbsencesPageState createState() => _AbsencesPageState(); _AbsencesPageState createState() => _AbsencesPageState();
} }
class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMixin { class _AbsencesPageState extends State<AbsencesPage>
with TickerProviderStateMixin {
late UserProvider user; late UserProvider user;
late AbsenceProvider absenceProvider; late AbsenceProvider absenceProvider;
late TimetableProvider timetableProvider; late TimetableProvider timetableProvider;
@ -65,7 +67,9 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
for (final lesson in timetableProvider.getWeek(Week.current()) ?? []) { 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( _lessonCount.update(
lesson.subject, lesson.subject,
(value) { (value) {
@ -89,25 +93,30 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
if (absence.delay != 0) continue; if (absence.delay != 0) continue;
if (!_absences.containsKey(absence.subject)) { if (!_absences.containsKey(absence.subject)) {
_absences[absence.subject] = SubjectAbsence(subject: absence.subject, absences: [absence]); _absences[absence.subject] =
SubjectAbsence(subject: absence.subject, absences: [absence]);
} else { } else {
_absences[absence.subject]?.absences.add(absence); _absences[absence.subject]?.absences.add(absence);
} }
} }
_absences.forEach((subject, 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; final totalLessonsOfSubject = _lessonCount[subject]?.lessonYearIndex ?? 0;
double absentLessonsOfSubjectPercentage; double absentLessonsOfSubjectPercentage;
if (absentLessonsOfSubject <= totalLessonsOfSubject) { if (absentLessonsOfSubject <= totalLessonsOfSubject) {
absentLessonsOfSubjectPercentage = absentLessonsOfSubject / totalLessonsOfSubject * 100; absentLessonsOfSubjectPercentage =
absentLessonsOfSubject / totalLessonsOfSubject * 100;
} else { } else {
absentLessonsOfSubjectPercentage = -1; absentLessonsOfSubjectPercentage = -1;
} }
_absences[subject]?.percentage = absentLessonsOfSubjectPercentage.clamp(-1, 100.0); _absences[subject]?.percentage =
absentLessonsOfSubjectPercentage.clamp(-1, 100.0);
}); });
absences = _absences.values.toList(); absences = _absences.values.toList();
@ -131,7 +140,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
body: Padding( body: Padding(
padding: const EdgeInsets.only(top: 12.0), padding: const EdgeInsets.only(top: 12.0),
child: NestedScrollView( child: NestedScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
headerSliverBuilder: (context, _) => [ headerSliverBuilder: (context, _) => [
SliverAppBar( SliverAppBar(
pinned: true, pinned: true,
@ -145,7 +155,10 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
"Absences".i18n, "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: [ bottom: FilterBar(items: [
@ -158,7 +171,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
body: TabBarView( body: TabBarView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
controller: _tabController, 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( widget: AbsenceSubjectTile(
a.subject, a.subject,
percentage: a.percentage, percentage: a.percentage,
excused: a.absences.where((a) => a.state == Justification.excused).length, excused: a.absences
unexcused: a.absences.where((a) => a.state == Justification.unexcused).length, .where((a) => a.state == Justification.excused)
pending: a.absences.where((a) => a.state == Justification.pending).length, .length,
onTap: () => AbsenceSubjectView.show(a.subject, a.absences, context: context), 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),
), ),
)); ));
} }
@ -186,15 +207,18 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
for (var absence in absenceProvider.absences) { for (var absence in absenceProvider.absences) {
if (absence.delay != 0) { if (absence.delay != 0) {
items.add(DateWidget( items.add(DateWidget(
date: absence.date, date: absence.date,
widget: AbsenceViewable(absence, padding: EdgeInsets.zero), widget: AbsenceViewable(
)); absence,
padding: EdgeInsets.zero,
)));
} }
} }
break; break;
case AbsenceFilter.misses: case AbsenceFilter.misses:
for (var note in noteProvider.notes) { 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( items.add(DateWidget(
date: note.date, date: note.date,
widget: MissTile(note), widget: MissTile(note),
@ -232,10 +256,15 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
title: Text("attention".i18n), title: Text("attention".i18n),
content: Text("attention_body".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( 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), itemCount: max(filterWidgets.length + (activeData <= 1 ? 1 : 0), 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (filterWidgets.isNotEmpty) { 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 value1 = 0;
int value2 = 0; int value2 = 0;
String title1 = ""; String title1 = "";
@ -292,18 +325,26 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
String suffix = ""; String suffix = "";
if (activeData == AbsenceFilter.absences.index) { if (activeData == AbsenceFilter.absences.index) {
value1 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.excused).length; value1 = absenceProvider.absences
value2 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.unexcused).length; .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; title1 = "stat_1".i18n;
title2 = "stat_2".i18n; title2 = "stat_2".i18n;
suffix = " " + "hr".i18n; suffix = " " + "hr".i18n;
} else if (activeData == AbsenceFilter.delays.index) { } else if (activeData == AbsenceFilter.delays.index) {
value1 = absenceProvider.absences 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) .map((e) => e.delay)
.fold(0, (a, b) => a + b); .fold(0, (a, b) => a + b);
value2 = absenceProvider.absences 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) .map((e) => e.delay)
.fold(0, (a, b) => a + b); .fold(0, (a, b) => a + b);
title1 = "stat_3".i18n; title1 = "stat_3".i18n;
@ -312,7 +353,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
} }
return Padding( 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: [ child: Row(children: [
Expanded( Expanded(
child: StatisticsTile( child: StatisticsTile(
@ -348,7 +390,8 @@ class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMix
} }
return Padding( 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)], child: filterWidgets[index - (activeData <= 1 ? 1 : 0)],
); );
} else { } else {

View File

@ -12,6 +12,7 @@ import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_premium/providers/goal_provider.dart';
class NavigationScreen extends StatefulWidget { class NavigationScreen extends StatefulWidget {
const NavigationScreen({Key? key}) : super(key: key); const NavigationScreen({Key? key}) : super(key: key);
@ -29,6 +30,7 @@ class NavigationScreenState extends State<NavigationScreen>
late NavigationRoute selected; late NavigationRoute selected;
late SettingsProvider settings; late SettingsProvider settings;
late NewsProvider newsProvider; late NewsProvider newsProvider;
late GoalProvider goalProvider;
double topInset = 0.0; double topInset = 0.0;
@override @override
@ -45,10 +47,14 @@ class NavigationScreenState extends State<NavigationScreen>
Provider.of<KretaClient>(context, listen: false).userAgent = Provider.of<KretaClient>(context, listen: false).userAgent =
settings.config.userAgent; settings.config.userAgent;
// Get news // get news
newsProvider = Provider.of<NewsProvider>(context, listen: false); newsProvider = Provider.of<NewsProvider>(context, listen: false);
newsProvider.restore().then((value) => newsProvider.fetch()); newsProvider.restore().then((value) => newsProvider.fetch());
// get goals
// goalProvider = Provider.of<GoalProvider>(context, listen: false);
// goalProvider.fetchDone();
// Initial sync // Initial sync
syncAll(context); syncAll(context);
@ -98,13 +104,17 @@ class NavigationScreenState extends State<NavigationScreen>
Widget build(BuildContext context) { Widget build(BuildContext context) {
settings = Provider.of<SettingsProvider>(context); settings = Provider.of<SettingsProvider>(context);
newsProvider = Provider.of<NewsProvider>(context); newsProvider = Provider.of<NewsProvider>(context);
goalProvider = Provider.of<GoalProvider>(context);
// Show news // show news / complete goals
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (newsProvider.show) { if (newsProvider.show) {
newsProvider.lock(); newsProvider.lock();
// NewsView.show(newsProvider.news[newsProvider.state], context: context).then((value) => newsProvider.release()); // NewsView.show(newsProvider.news[newsProvider.state], context: context).then((value) => newsProvider.release());
} }
if (goalProvider.hasDoneGoals) {
// to-do
}
}); });
return Scaffold( return Scaffold(

View File

@ -1,6 +1,4 @@
import 'package:filcnaplo/api/providers/update_provider.dart'; 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/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart'; import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart'; import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';

View File

@ -66,8 +66,12 @@ class KretaClient {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (autoHeader) { if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) headerMap["authorization"] = "Bearer $accessToken"; if (!headerMap.containsKey("authorization") && accessToken != null) {
if (!headerMap.containsKey("user-agent") && userAgent != null) headerMap["user-agent"] = "$userAgent"; headerMap["authorization"] = "Bearer $accessToken";
}
if (!headerMap.containsKey("user-agent") && userAgent != null) {
headerMap["user-agent"] = "$userAgent";
}
} }
res = await client.get(Uri.parse(url), headers: headerMap); res = await client.get(Uri.parse(url), headers: headerMap);
@ -94,7 +98,8 @@ class KretaClient {
return res.body; return res.body;
} }
} on http.ClientException catch (error) { } on http.ClientException catch (error) {
print("ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}"); print(
"ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}");
} catch (error) { } catch (error) {
print("ERROR: KretaClient.getAPI ($url) ${error.runtimeType}: $error"); print("ERROR: KretaClient.getAPI ($url) ${error.runtimeType}: $error");
} }
@ -120,9 +125,15 @@ class KretaClient {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (autoHeader) { if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) headerMap["authorization"] = "Bearer $accessToken"; if (!headerMap.containsKey("authorization") && accessToken != null) {
if (!headerMap.containsKey("user-agent") && userAgent != null) headerMap["user-agent"] = "$userAgent"; headerMap["authorization"] = "Bearer $accessToken";
if (!headerMap.containsKey("content-type")) headerMap["content-type"] = "application/json"; }
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); res = await client.post(Uri.parse(url), headers: headerMap, body: body);
@ -142,9 +153,10 @@ class KretaClient {
return res.body; return res.body;
} }
} on http.ClientException catch (error) { } on http.ClientException catch (error) {
print("ERROR: KretaClient.getAPI ($url) ClientException: ${error.message}"); print(
"ERROR: KretaClient.postAPI ($url) ClientException: ${error.message}");
} catch (error) { } 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); 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()); headers.addAll(nonce.header());
if (_settings.presentationMode) { if (_settings.presentationMode) {
@ -175,18 +188,28 @@ class KretaClient {
)); ));
if (loginRes != null) { if (loginRes != null) {
if (loginRes.containsKey("access_token")) accessToken = loginRes["access_token"]; if (loginRes.containsKey("access_token")) {
if (loginRes.containsKey("refresh_token")) refreshToken = loginRes["refresh_token"]; accessToken = loginRes["access_token"];
}
if (loginRes.containsKey("refresh_token")) {
refreshToken = loginRes["refresh_token"];
}
// Update role // Update role
loginUser.role = JwtUtils.getRoleFromJWT(accessToken ?? "") ?? Role.student; loginUser.role =
JwtUtils.getRoleFromJWT(accessToken ?? "") ?? Role.student;
} }
if (refreshToken != null) { if (refreshToken != null) {
Map? refreshRes = await postAPI(KretaAPI.login, 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 != null) {
if (refreshRes.containsKey("id_token")) idToken = refreshRes["id_token"]; if (refreshRes.containsKey("id_token")) {
idToken = refreshRes["id_token"];
}
} }
} }
} }

View File

@ -24,7 +24,10 @@ class TimetableController extends ChangeNotifier {
current(); 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() { static DateTime getSchoolYearStart() {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
@ -48,8 +51,10 @@ class TimetableController extends ChangeNotifier {
} }
// Jump shortcuts // Jump shortcuts
Future<void> next(BuildContext context) => jump(Week.fromId(currentWeekId + 1), context: context); Future<void> next(BuildContext context) =>
Future<void> previous(BuildContext context) => jump(Week.fromId(currentWeekId - 1), context: context); jump(Week.fromId(currentWeekId + 1), context: context);
Future<void> previous(BuildContext context) =>
jump(Week.fromId(currentWeekId - 1), context: context);
void current() { void current() {
Week week = Week.current(); Week week = Week.current();
int id = getWeekId(week); int id = getWeekId(week);
@ -60,7 +65,11 @@ class TimetableController extends ChangeNotifier {
_setWeek(Week.fromId(id)); _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; if (_setWeek(week)) return;
loadType = LoadType.initial; loadType = LoadType.initial;
@ -81,7 +90,9 @@ class TimetableController extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { 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; loadType = LoadType.online;
} catch (error, stack) { } catch (error, stack) {
print("ERROR: TimetableController.jump: $error\n$stack"); print("ERROR: TimetableController.jump: $error\n$stack");
@ -95,7 +106,9 @@ class TimetableController extends ChangeNotifier {
if (week != currentWeek) return; if (week != currentWeek) return;
// Jump to next week on weekends // 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(); notifyListeners();
} }
@ -106,7 +119,9 @@ class TimetableController extends ChangeNotifier {
if (id < 0) return true; // Min 1. if (id < 0) return true; // Min 1.
// Set week start to Sept. 1 of first week // 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; currentWeek = week;
previousWeekId = currentWeekId; previousWeekId = currentWeekId;
@ -124,16 +139,17 @@ class TimetableController extends ChangeNotifier {
List<List<Lesson>> _sortDays(Week week, {required BuildContext context}) { List<List<Lesson>> _sortDays(Week week, {required BuildContext context}) {
List<List<Lesson>> days = []; List<List<Lesson>> days = [];
final timetableProvider = context.read<TimetableProvider>();
try { try {
final timetableProvider = context.read<TimetableProvider>();
List<Lesson> lessons = timetableProvider.getWeek(week) ?? []; List<Lesson> lessons = timetableProvider.getWeek(week) ?? [];
if (lessons.isNotEmpty) { if (lessons.isNotEmpty) {
days.add([]); days.add([]);
lessons.sort((a, b) => a.date.compareTo(b.date)); lessons.sort((a, b) => a.date.compareTo(b.date));
for (var lesson in lessons) { 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); days.last.add(lesson);
} }
@ -152,7 +168,8 @@ class TimetableController extends ChangeNotifier {
if (lessonIndexes.isNotEmpty) { if (lessonIndexes.isNotEmpty) {
// Fill missing indexes with empty spaces // 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); List<Lesson> indexLessons = _getLessonsByIndex(_day, i);
// Empty lesson // Empty lesson
@ -160,8 +177,13 @@ class TimetableController extends ChangeNotifier {
// Get start date by previous lesson // Get start date by previous lesson
List<Lesson> prevLesson = _getLessonsByIndex(day, i - 1); List<Lesson> prevLesson = _getLessonsByIndex(day, i - 1);
try { try {
DateTime? startDate = prevLesson.last.start.add(const Duration(seconds: 1)); DateTime startDate =
indexLessons.add(Lesson.fromJson({'isEmpty': true, 'Oraszam': i, 'KezdetIdopont': startDate.toIso8601String()})); prevLesson.last.start.add(const Duration(seconds: 1));
indexLessons.add(Lesson.fromJson({
'isEmpty': true,
'Oraszam': i,
'KezdetIdopont': startDate.toIso8601String()
}));
// ignore: empty_catches // ignore: empty_catches
} catch (e) {} } catch (e) {}
} }
@ -171,7 +193,8 @@ class TimetableController extends ChangeNotifier {
} }
// Additional lessons // 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)); day.sort((a, b) => a.start.compareTo(b.start));
@ -213,5 +236,6 @@ class TimetableController extends ChangeNotifier {
return indexes; 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);
} }

View File

@ -1,5 +1,6 @@
import "category.dart"; import "category.dart";
import "subject.dart"; import "subject.dart";
import "teacher.dart";
class Absence { class Absence {
Map? json; Map? json;
@ -7,7 +8,7 @@ class Absence {
DateTime date; DateTime date;
int delay; int delay;
DateTime submitDate; DateTime submitDate;
String teacher; Teacher teacher;
Justification state; Justification state;
Category? justification; Category? justification;
Category? type; Category? type;
@ -17,6 +18,14 @@ class Absence {
DateTime lessonEnd; DateTime lessonEnd;
int? lessonIndex; int? lessonIndex;
String group; String group;
bool isSeen;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Absence && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
Absence({ Absence({
required this.id, required this.id,
@ -34,6 +43,7 @@ class Absence {
this.lessonIndex, this.lessonIndex,
required this.group, required this.group,
this.json, this.json,
this.isSeen = false,
}); });
factory Absence.fromJson(Map json) { factory Absence.fromJson(Map json) {
@ -41,8 +51,12 @@ class Absence {
DateTime lessonEnd; DateTime lessonEnd;
int? lessonIndex; int? lessonIndex;
if (json["Ora"] != null) { if (json["Ora"] != null) {
lessonStart = json["Ora"]["KezdoDatum"] != null ? DateTime.parse(json["Ora"]["KezdoDatum"]).toLocal() : DateTime(0); lessonStart = json["Ora"]["KezdoDatum"] != null
lessonEnd = json["Ora"]["VegDatum"] != null ? DateTime.parse(json["Ora"]["VegDatum"]).toLocal() : DateTime(0); ? 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"]; lessonIndex = json["Ora"]["Oraszam"];
} else { } else {
lessonStart = DateTime(0); lessonStart = DateTime(0);
@ -51,26 +65,34 @@ class Absence {
return Absence( return Absence(
id: json["Uid"], 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, delay: json["KesesPercben"] ?? 0,
submitDate: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0), submitDate: json["KeszitesDatuma"] != null
teacher: (json["RogzitoTanarNeve"] ?? "").trim(), ? DateTime.parse(json["KeszitesDatuma"]).toLocal()
: DateTime(0),
teacher: Teacher.fromString((json["RogzitoTanarNeve"] ?? "").trim()),
state: json["IgazolasAllapota"] == "Igazolt" state: json["IgazolasAllapota"] == "Igazolt"
? Justification.excused ? Justification.excused
: json["IgazolasAllapota"] == "Igazolando" : json["IgazolasAllapota"] == "Igazolando"
? Justification.pending ? Justification.pending
: Justification.unexcused, : 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, type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null,
mode: json["Mod"] != null ? Category.fromJson(json["Mod"]) : null, mode: json["Mod"] != null ? Category.fromJson(json["Mod"]) : null,
subject: Subject.fromJson(json["Tantargy"] ?? {}), subject: Subject.fromJson(json["Tantargy"] ?? {}),
lessonStart: lessonStart, lessonStart: lessonStart,
lessonEnd: lessonEnd, lessonEnd: lessonEnd,
lessonIndex: lessonIndex, lessonIndex: lessonIndex,
group: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] : "", group:
json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] : "",
isSeen: false,
json: json, json: json,
); );
} }
} }
enum Justification { excused, unexcused, pending } enum Justification { excused, unexcused, pending }

View File

@ -1,4 +1,5 @@
import 'category.dart'; import 'category.dart';
import 'teacher.dart';
class Exam { class Exam {
Map? json; Map? json;
@ -7,7 +8,7 @@ class Exam {
Category? mode; Category? mode;
int? subjectIndex; int? subjectIndex;
String subjectName; String subjectName;
String teacher; Teacher teacher;
String description; String description;
String group; String group;
String id; String id;
@ -28,14 +29,20 @@ class Exam {
factory Exam.fromJson(Map json) { factory Exam.fromJson(Map json) {
return Exam( return Exam(
id: json["Uid"] ?? "", id: json["Uid"] ?? "",
date: json["BejelentesDatuma"] != null ? DateTime.parse(json["BejelentesDatuma"]).toLocal() : DateTime(0), date: json["BejelentesDatuma"] != null
writeDate: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0), ? 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, mode: json["Modja"] != null ? Category.fromJson(json["Modja"]) : null,
subjectIndex: json["OrarendiOraOraszama"], subjectIndex: json["OrarendiOraOraszama"],
subjectName: json["TantargyNeve"] ?? "", subjectName: json["TantargyNeve"] ?? "",
teacher: (json["RogzitoTanarNeve"] ?? "").trim(), teacher: Teacher.fromString((json["RogzitoTanarNeve"] ?? "").trim()),
description: (json["Temaja"] ?? "").trim(), description: (json["Temaja"] ?? "").trim(),
group: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] ?? "" : "", group: json["OsztalyCsoport"] != null
? json["OsztalyCsoport"]["Uid"] ?? ""
: "",
json: json, json: json,
); );
} }

View File

@ -1,13 +1,14 @@
import 'package:filcnaplo/utils/format.dart'; import 'package:filcnaplo/utils/format.dart';
import 'category.dart'; import 'category.dart';
import 'subject.dart'; import 'subject.dart';
import 'teacher.dart';
class Grade { class Grade {
Map? json; Map? json;
String id; String id;
DateTime date; DateTime date;
GradeValue value; GradeValue value;
String teacher; Teacher teacher;
String description; String description;
GradeType type; GradeType type;
String groupId; String groupId;
@ -38,23 +39,35 @@ class Grade {
factory Grade.fromJson(Map json) { factory Grade.fromJson(Map json) {
return Grade( return Grade(
id: json["Uid"] ?? "", id: json["Uid"] ?? "",
date: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0), date: json["KeszitesDatuma"] != null
? DateTime.parse(json["KeszitesDatuma"]).toLocal()
: DateTime(0),
value: GradeValue( value: GradeValue(
json["SzamErtek"] ?? 0, json["SzamErtek"] ?? 0,
json["SzovegesErtek"] ?? "", json["SzovegesErtek"] ?? "",
json["SzovegesErtekelesRovidNev"] ?? "", json["SzovegesErtekelesRovidNev"] ?? "",
json["SulySzazalekErteke"] ?? 0, json["SulySzazalekErteke"] ?? 0,
percentage: json["ErtekFajta"] != null ? json["ErtekFajta"]["Uid"] == "3,Szazalekos" : false, percentage: json["ErtekFajta"] != null
? json["ErtekFajta"]["Uid"] == "3,Szazalekos"
: false,
), ),
teacher: (json["ErtekeloTanarNeve"] ?? "").trim(), teacher: Teacher.fromString((json["ErtekeloTanarNeve"] ?? "").trim()),
description: json["Tema"] ?? "", description: json["Tema"] ?? "",
type: json["Tipus"] != null ? Category.getGradeType(json["Tipus"]["Nev"]) : GradeType.unknown, type: json["Tipus"] != null
? Category.getGradeType(json["Tipus"]["Nev"])
: GradeType.unknown,
groupId: (json["OsztalyCsoport"] ?? {})["Uid"] ?? "", groupId: (json["OsztalyCsoport"] ?? {})["Uid"] ?? "",
subject: Subject.fromJson(json["Tantargy"] ?? {}), subject: Subject.fromJson(json["Tantargy"] ?? {}),
gradeType: json["ErtekFajta"] != null ? Category.fromJson(json["ErtekFajta"]) : null, gradeType: json["ErtekFajta"] != null
? Category.fromJson(json["ErtekFajta"])
: null,
mode: Category.fromJson(json["Mod"] ?? {}), mode: Category.fromJson(json["Mod"] ?? {}),
writeDate: json["RogzitesDatuma"] != null ? DateTime.parse(json["RogzitesDatuma"]).toLocal() : DateTime(0), writeDate: json["RogzitesDatuma"] != null
seenDate: json["LattamozasDatuma"] != null ? DateTime.parse(json["LattamozasDatuma"]).toLocal() : DateTime(0), ? DateTime.parse(json["RogzitesDatuma"]).toLocal()
: DateTime(0),
seenDate: json["LattamozasDatuma"] != null
? DateTime.parse(json["LattamozasDatuma"]).toLocal()
: DateTime(0),
form: (json["Jelleg"] ?? "Na") != "Na" ? json["Jelleg"] : "", form: (json["Jelleg"] ?? "Na") != "Na" ? json["Jelleg"] : "",
json: json, json: json,
); );
@ -76,7 +89,8 @@ class GradeValue {
set value(int v) => _value = v; set value(int v) => _value = v;
int get value { int get value {
String _valueName = valueName.toLowerCase().specialChars(); String _valueName = valueName.toLowerCase().specialChars();
if (_value == 0 && ["peldas", "jo", "valtozo", "rossz", "hanyag"].contains(_valueName)) { if (_value == 0 &&
["peldas", "jo", "valtozo", "rossz", "hanyag"].contains(_valueName)) {
switch (_valueName) { switch (_valueName) {
case "peldas": case "peldas":
return 5; return 5;
@ -87,7 +101,16 @@ class GradeValue {
case "rossz": case "rossz":
return 2; return 2;
case "hanyag": case "hanyag":
return 1;
// other
case "jeles":
return 5;
case "kozepes":
return 3;
case "elegseges":
return 2; return 2;
case "elegtelen":
return 1;
} }
} }
return _value; return _value;
@ -101,7 +124,8 @@ class GradeValue {
set weight(int v) => _weight = v; set weight(int v) => _weight = v;
int get weight { int get weight {
String _valueName = valueName.toLowerCase().specialChars(); String _valueName = valueName.toLowerCase().specialChars();
if (_value == 0 && ["peldas", "jo", "valtozo", "rossz", "hanyag"].contains(_valueName)) { if (_value == 0 &&
["peldas", "jo", "valtozo", "rossz", "hanyag"].contains(_valueName)) {
return 0; return 0;
} }
return _weight; return _weight;
@ -110,11 +134,23 @@ class GradeValue {
final bool _percentage; final bool _percentage;
bool get percentage => _percentage; bool get percentage => _percentage;
GradeValue(int value, String valueName, this.shortName, int weight, {bool percentage = false}) GradeValue(int value, String valueName, this.shortName, int weight,
{bool percentage = false})
: _value = value, : _value = value,
_valueName = valueName, _valueName = valueName,
_weight = weight, _weight = weight,
_percentage = percentage; _percentage = percentage;
} }
enum GradeType { midYear, firstQ, secondQ, halfYear, thirdQ, fourthQ, endYear, levelExam, ghost, unknown } enum GradeType {
midYear,
firstQ,
secondQ,
halfYear,
thirdQ,
fourthQ,
endYear,
levelExam,
ghost,
unknown
}

View File

@ -1,6 +1,7 @@
import 'package:filcnaplo_kreta_api/client/api.dart'; import 'package:filcnaplo_kreta_api/client/api.dart';
import 'subject.dart'; import 'subject.dart';
import 'teacher.dart';
class Homework { class Homework {
Map? json; Map? json;
@ -9,7 +10,7 @@ class Homework {
DateTime deadline; DateTime deadline;
bool byTeacher; bool byTeacher;
bool homeworkEnabled; bool homeworkEnabled;
String teacher; Teacher teacher;
String content; String content;
Subject subject; Subject subject;
String group; String group;
@ -45,7 +46,7 @@ class Homework {
: DateTime(0), : DateTime(0),
byTeacher: json["IsTanarRogzitette"] ?? true, byTeacher: json["IsTanarRogzitette"] ?? true,
homeworkEnabled: json["IsTanuloHaziFeladatEnabled"] ?? false, homeworkEnabled: json["IsTanuloHaziFeladatEnabled"] ?? false,
teacher: (json["RogzitoTanarNeve"] ?? "").trim(), teacher: Teacher.fromString((json["RogzitoTanarNeve"] ?? "").trim()),
content: (json["Szoveg"] ?? "").trim(), content: (json["Szoveg"] ?? "").trim(),
subject: Subject.fromJson(json["Tantargy"] ?? {}), subject: Subject.fromJson(json["Tantargy"] ?? {}),
group: json["OsztalyCsoport"] != null group: json["OsztalyCsoport"] != null

View File

@ -1,5 +1,6 @@
import 'subject.dart'; import 'subject.dart';
import 'category.dart'; import 'category.dart';
import 'teacher.dart';
class Lesson { class Lesson {
Map? json; Map? json;
@ -8,8 +9,8 @@ class Lesson {
Subject subject; Subject subject;
String lessonIndex; String lessonIndex;
int? lessonYearIndex; int? lessonYearIndex;
String substituteTeacher; Teacher? substituteTeacher;
String teacher; Teacher teacher;
bool homeworkEnabled; bool homeworkEnabled;
DateTime start; DateTime start;
DateTime end; DateTime end;
@ -24,6 +25,7 @@ class Lesson {
String name; String name;
bool online; bool online;
bool isEmpty; bool isEmpty;
bool isSeen;
Lesson({ Lesson({
this.status, this.status,
@ -31,7 +33,7 @@ class Lesson {
required this.subject, required this.subject,
required this.lessonIndex, required this.lessonIndex,
this.lessonYearIndex, this.lessonYearIndex,
this.substituteTeacher = "", this.substituteTeacher,
required this.teacher, required this.teacher,
this.homeworkEnabled = false, this.homeworkEnabled = false,
required this.start, required this.start,
@ -48,36 +50,56 @@ class Lesson {
this.online = false, this.online = false,
this.isEmpty = false, this.isEmpty = false,
this.json, this.json,
this.isSeen = false,
}); });
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Lesson && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
factory Lesson.fromJson(Map json) { factory Lesson.fromJson(Map json) {
return Lesson( return Lesson(
id: json["Uid"] ?? "", id: json["Uid"] ?? "",
status: json["Allapot"] != null ? Category.fromJson(json["Allapot"]) : null, status:
date: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0), json["Allapot"] != null ? Category.fromJson(json["Allapot"]) : null,
date: json["Datum"] != null
? DateTime.parse(json["Datum"]).toLocal()
: DateTime(0),
subject: Subject.fromJson(json["Tantargy"] ?? {}), subject: Subject.fromJson(json["Tantargy"] ?? {}),
lessonIndex: json["Oraszam"] != null ? json["Oraszam"].toString() : "+", lessonIndex: json["Oraszam"] != null ? json["Oraszam"].toString() : "+",
lessonYearIndex: json["OraEvesSorszama"], lessonYearIndex: json["OraEvesSorszama"],
substituteTeacher: (json["HelyettesTanarNeve"] ?? "").trim(), substituteTeacher:
teacher: (json["TanarNeve"] ?? "").trim(), Teacher.fromString((json["HelyettesTanarNeve"] ?? "").trim()),
teacher: Teacher.fromString((json["TanarNeve"] ?? "").trim()),
homeworkEnabled: json["IsTanuloHaziFeladatEnabled"] ?? false, homeworkEnabled: json["IsTanuloHaziFeladatEnabled"] ?? false,
start: json["KezdetIdopont"] != null ? DateTime.parse(json["KezdetIdopont"]).toLocal() : DateTime(0), start: json["KezdetIdopont"] != null
? DateTime.parse(json["KezdetIdopont"]).toLocal()
: DateTime(0),
studentPresence: json["TanuloJelenlet"] != null studentPresence: json["TanuloJelenlet"] != null
? (json["TanuloJelenlet"]["Nev"] ?? "") == "Hianyzas" ? (json["TanuloJelenlet"]["Nev"] ?? "") == "Hianyzas"
? false ? false
: true : true
: true, : true,
end: json["VegIdopont"] != null ? DateTime.parse(json["VegIdopont"]).toLocal() : DateTime(0), end: json["VegIdopont"] != null
? DateTime.parse(json["VegIdopont"]).toLocal()
: DateTime(0),
homeworkId: json["HaziFeladatUid"] ?? "", homeworkId: json["HaziFeladatUid"] ?? "",
exam: json["BejelentettSzamonkeresUid"] ?? "", exam: json["BejelentettSzamonkeresUid"] ?? "",
type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null, type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null,
description: json["Tema"] ?? "", description: json["Tema"] ?? "",
room: ((json["TeremNeve"] ?? "").split("_").join(" ") as String).replaceAll(RegExp(r" ?terem ?", caseSensitive: false), ""), room: ((json["TeremNeve"] ?? "").split("_").join(" ") as String)
groupName: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Nev"] ?? "" : "", .replaceAll(RegExp(r" ?terem ?", caseSensitive: false), ""),
groupName: json["OsztalyCsoport"] != null
? json["OsztalyCsoport"]["Nev"] ?? ""
: "",
name: json["Nev"] ?? "", name: json["Nev"] ?? "",
online: json["IsDigitalisOra"] ?? false, online: json["IsDigitalisOra"] ?? false,
isEmpty: json['isEmpty'] ?? false, isEmpty: json['isEmpty'] ?? false,
json: json, json: json,
isSeen: false
); );
} }
@ -92,6 +114,6 @@ class Lesson {
return null; return null;
} }
bool get isChanged => status?.name == "Elmaradt" || substituteTeacher != ""; bool get isChanged => status?.name == "Elmaradt" || substituteTeacher != null;
bool get swapDesc => room.length > 8; bool get swapDesc => room.length > 8;
} }

View File

@ -16,6 +16,7 @@ class Message {
MessageType? type; MessageType? type;
List<Recipient> recipients; List<Recipient> recipients;
List<Attachment> attachments; List<Attachment> attachments;
bool isSeen;
Message({ Message({
required this.id, required this.id,
@ -32,7 +33,15 @@ class Message {
this.replyId, this.replyId,
this.conversationId, this.conversationId,
this.json, this.json,
this.isSeen = false,
}); });
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Message && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
factory Message.fromJson(Map json, {MessageType? forceType}) { factory Message.fromJson(Map json, {MessageType? forceType}) {
Map message = json["uzenet"]; Map message = json["uzenet"];
@ -69,6 +78,7 @@ class Message {
replyId: message["elozoUzenetAzonosito"], replyId: message["elozoUzenetAzonosito"],
conversationId: message["beszelgetesAzonosito"], conversationId: message["beszelgetesAzonosito"],
json: json, json: json,
isSeen: false,
); );
} }

View File

@ -1,4 +1,5 @@
import 'category.dart'; import 'category.dart';
import 'teacher.dart';
class Note { class Note {
Map? json; Map? json;
@ -6,7 +7,7 @@ class Note {
String title; String title;
DateTime date; DateTime date;
DateTime submitDate; DateTime submitDate;
String teacher; Teacher teacher;
DateTime seenDate; DateTime seenDate;
String groupId; String groupId;
String content; String content;
@ -29,11 +30,19 @@ class Note {
return Note( return Note(
id: json["Uid"] ?? "", id: json["Uid"] ?? "",
title: json["Cim"] ?? "", title: json["Cim"] ?? "",
date: json["Datum"] != null ? DateTime.parse(json["Datum"]).toLocal() : DateTime(0), date: json["Datum"] != null
submitDate: json["KeszitesDatuma"] != null ? DateTime.parse(json["KeszitesDatuma"]).toLocal() : DateTime(0), ? DateTime.parse(json["Datum"]).toLocal()
teacher: (json["KeszitoTanarNeve"] ?? "").trim(), : DateTime(0),
seenDate: json["LattamozasDatuma"] != null ? DateTime.parse(json["LattamozasDatuma"]).toLocal() : DateTime(0), submitDate: json["KeszitesDatuma"] != null
groupId: json["OsztalyCsoport"] != null ? json["OsztalyCsoport"]["Uid"] ?? "" : "", ? DateTime.parse(json["KeszitesDatuma"]).toLocal()
: DateTime(0),
teacher: Teacher.fromString((json["KeszitoTanarNeve"] ?? "").trim()),
seenDate: json["LattamozasDatuma"] != null
? DateTime.parse(json["LattamozasDatuma"]).toLocal()
: DateTime(0),
groupId: json["OsztalyCsoport"] != null
? json["OsztalyCsoport"]["Uid"] ?? ""
: "",
content: json["Tartalom"].replaceAll("\r", "") ?? "", content: json["Tartalom"].replaceAll("\r", "") ?? "",
type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null, type: json["Tipus"] != null ? Category.fromJson(json["Tipus"]) : null,
json: json, json: json,

View File

@ -0,0 +1,34 @@
import 'package:filcnaplo/utils/format.dart';
class Teacher {
String id;
String name;
String? renamedTo;
bool get isRenamed => renamedTo != null;
Teacher({required this.id, required this.name, this.renamedTo});
factory Teacher.fromJson(Map json) {
return Teacher(
id: json["Uid"] ?? "",
name: (json["Nev"] ?? "").trim(),
);
}
factory Teacher.fromString(String string) {
return Teacher(
id: string.trim().replaceAll(' ', '').toLowerCase().specialChars(),
name: string.trim(),
);
}
@override
bool operator ==(other) {
if (other is! Teacher) return false;
return id == other.id;
}
@override
int get hashCode => id.hashCode;
}

View File

@ -27,7 +27,10 @@ class AbsenceProvider with ChangeNotifier {
// Load absences from the database // Load absences from the database
if (userId != null) { if (userId != null) {
var dbAbsences = await Provider.of<DatabaseProvider>(_context, listen: false).userQuery.getAbsences(userId: userId); var dbAbsences =
await Provider.of<DatabaseProvider>(_context, listen: false)
.userQuery
.getAbsences(userId: userId);
_absences = dbAbsences; _absences = dbAbsences;
await convertBySettings(); await convertBySettings();
} }
@ -36,12 +39,26 @@ class AbsenceProvider with ChangeNotifier {
// for renamed subjects // for renamed subjects
Future<void> convertBySettings() async { Future<void> convertBySettings() async {
final _database = Provider.of<DatabaseProvider>(_context, listen: false); final _database = Provider.of<DatabaseProvider>(_context, listen: false);
Map<String, String> renamedSubjects = (await _database.query.getSettings(_database)).renamedSubjectsEnabled Map<String, String> renamedSubjects =
? await _database.userQuery.renamedSubjects(userId: Provider.of<UserProvider>(_context, listen: false).user!.id) (await _database.query.getSettings(_database)).renamedSubjectsEnabled
: {}; ? await _database.userQuery.renamedSubjects(
userId:
Provider.of<UserProvider>(_context, listen: false).user!.id)
: {};
Map<String, String> renamedTeachers =
(await _database.query.getSettings(_database)).renamedTeachersEnabled
? await _database.userQuery.renamedTeachers(
userId:
Provider.of<UserProvider>(_context, listen: false).user!.id)
: {};
for (Absence absence in _absences) { for (Absence absence in _absences) {
absence.subject.renamedTo = renamedSubjects.isNotEmpty ? renamedSubjects[absence.subject.id] : null; absence.subject.renamedTo = renamedSubjects.isNotEmpty
? renamedSubjects[absence.subject.id]
: null;
absence.teacher.renamedTo = renamedTeachers.isNotEmpty
? renamedTeachers[absence.teacher.id]
: null;
} }
notifyListeners(); notifyListeners();
@ -53,9 +70,11 @@ class AbsenceProvider with ChangeNotifier {
if (user == null) throw "Cannot fetch Absences for User null"; if (user == null) throw "Cannot fetch Absences for User null";
String iss = user.instituteCode; String iss = user.instituteCode;
List? absencesJson = await Provider.of<KretaClient>(_context, listen: false).getAPI(KretaAPI.absences(iss)); List? absencesJson = await Provider.of<KretaClient>(_context, listen: false)
.getAPI(KretaAPI.absences(iss));
if (absencesJson == null) throw "Cannot fetch Absences for User ${user.id}"; if (absencesJson == null) throw "Cannot fetch Absences for User ${user.id}";
List<Absence> absences = absencesJson.map((e) => Absence.fromJson(e)).toList(); List<Absence> absences =
absencesJson.map((e) => Absence.fromJson(e)).toList();
if (absences.isNotEmpty || _absences.isNotEmpty) await store(absences); if (absences.isNotEmpty || _absences.isNotEmpty) await store(absences);
} }
@ -66,7 +85,9 @@ class AbsenceProvider with ChangeNotifier {
if (user == null) throw "Cannot store Absences for User null"; if (user == null) throw "Cannot store Absences for User null";
String userId = user.id; String userId = user.id;
await Provider.of<DatabaseProvider>(_context, listen: false).userStore.storeAbsences(absences, userId: userId); await Provider.of<DatabaseProvider>(_context, listen: false)
.userStore
.storeAbsences(absences, userId: userId);
_absences = absences; _absences = absences;
await convertBySettings(); await convertBySettings();
} }

View File

@ -83,10 +83,15 @@ class GradeProvider with ChangeNotifier {
Map<String, String> renamedSubjects = _settings.renamedSubjectsEnabled Map<String, String> renamedSubjects = _settings.renamedSubjectsEnabled
? await _database.userQuery.renamedSubjects(userId: _user.user!.id) ? await _database.userQuery.renamedSubjects(userId: _user.user!.id)
: {}; : {};
Map<String, String> renamedTeachers = _settings.renamedTeachersEnabled
? await _database.userQuery.renamedTeachers(userId: _user.user!.id)
: {};
for (Grade grade in _grades) { for (Grade grade in _grades) {
grade.subject.renamedTo = grade.subject.renamedTo =
renamedSubjects.isNotEmpty ? renamedSubjects[grade.subject.id] : null; renamedSubjects.isNotEmpty ? renamedSubjects[grade.subject.id] : null;
grade.teacher.renamedTo =
renamedTeachers.isNotEmpty ? renamedTeachers[grade.teacher.id] : null;
grade.value.value = grade.value.value =
_settings.goodStudent ? 5 : grade.json!["SzamErtek"] ?? 0; _settings.goodStudent ? 5 : grade.json!["SzamErtek"] ?? 0;

View File

@ -22,9 +22,11 @@ class HomeworkProvider with ChangeNotifier {
HomeworkProvider({ HomeworkProvider({
List<Homework> initialHomework = const [], List<Homework> initialHomework = const [],
required BuildContext context, required BuildContext context,
required DatabaseProvider database,
}) { }) {
_homework = List.castFrom(initialHomework); _homework = List.castFrom(initialHomework);
_context = context; _context = context;
_database = database;
if (_homework.isEmpty) restore(); if (_homework.isEmpty) restore();
} }
@ -48,11 +50,18 @@ class HomeworkProvider with ChangeNotifier {
(await _database.query.getSettings(_database)).renamedSubjectsEnabled (await _database.query.getSettings(_database)).renamedSubjectsEnabled
? await _database.userQuery.renamedSubjects(userId: _user.id!) ? await _database.userQuery.renamedSubjects(userId: _user.id!)
: {}; : {};
Map<String, String> renamedTeachers =
(await _database.query.getSettings(_database)).renamedTeachersEnabled
? await _database.userQuery.renamedTeachers(userId: _user.id!)
: {};
for (Homework homework in _homework) { for (Homework homework in _homework) {
homework.subject.renamedTo = renamedSubjects.isNotEmpty homework.subject.renamedTo = renamedSubjects.isNotEmpty
? renamedSubjects[homework.subject.id] ? renamedSubjects[homework.subject.id]
: null; : null;
homework.teacher.renamedTo = renamedTeachers.isNotEmpty
? renamedTeachers[homework.teacher.id]
: null;
} }
notifyListeners(); notifyListeners();

View File

@ -8,7 +8,7 @@ import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class TimetableProvider with ChangeNotifier { class TimetableProvider with ChangeNotifier {
Map<Week, List<Lesson>> _lessons = {}; Map<Week, List<Lesson>> lessons = {};
late final UserProvider _user; late final UserProvider _user;
late final DatabaseProvider _database; late final DatabaseProvider _database;
late final KretaClient _kreta; late final KretaClient _kreta;
@ -29,7 +29,7 @@ class TimetableProvider with ChangeNotifier {
// Load lessons from the database // Load lessons from the database
if (userId != null) { if (userId != null) {
var dbLessons = await _database.userQuery.getLessons(userId: userId); var dbLessons = await _database.userQuery.getLessons(userId: userId);
_lessons = dbLessons; lessons = dbLessons;
await convertBySettings(); await convertBySettings();
} }
} }
@ -40,17 +40,24 @@ class TimetableProvider with ChangeNotifier {
(await _database.query.getSettings(_database)).renamedSubjectsEnabled (await _database.query.getSettings(_database)).renamedSubjectsEnabled
? await _database.userQuery.renamedSubjects(userId: _user.id!) ? await _database.userQuery.renamedSubjects(userId: _user.id!)
: {}; : {};
Map<String, String> renamedTeachers =
(await _database.query.getSettings(_database)).renamedTeachersEnabled
? await _database.userQuery.renamedTeachers(userId: _user.id!)
: {};
for (Lesson lesson in _lessons.values.expand((e) => e)) { for (Lesson lesson in lessons.values.expand((e) => e)) {
lesson.subject.renamedTo = renamedSubjects.isNotEmpty lesson.subject.renamedTo = renamedSubjects.isNotEmpty
? renamedSubjects[lesson.subject.id] ? renamedSubjects[lesson.subject.id]
: null; : null;
lesson.teacher.renamedTo = renamedTeachers.isNotEmpty
? renamedTeachers[lesson.teacher.id]
: null;
} }
notifyListeners(); notifyListeners();
} }
List<Lesson>? getWeek(Week week) => _lessons[week]; List<Lesson>? getWeek(Week week) => lessons[week];
// Fetches Lessons from the Kreta API then stores them in the database // Fetches Lessons from the Kreta API then stores them in the database
Future<void> fetch({Week? week}) async { Future<void> fetch({Week? week}) async {
@ -61,11 +68,11 @@ class TimetableProvider with ChangeNotifier {
List? lessonsJson = await _kreta List? lessonsJson = await _kreta
.getAPI(KretaAPI.timetable(iss, start: week.start, end: week.end)); .getAPI(KretaAPI.timetable(iss, start: week.start, end: week.end));
if (lessonsJson == null) throw "Cannot fetch Lessons for User ${user.id}"; if (lessonsJson == null) throw "Cannot fetch Lessons for User ${user.id}";
List<Lesson> lessons = lessonsJson.map((e) => Lesson.fromJson(e)).toList(); List<Lesson> lessonsList = lessonsJson.map((e) => Lesson.fromJson(e)).toList();
if (lessons.isEmpty && _lessons.isEmpty) return; if (lessons.isEmpty && lessons.isEmpty) return;
_lessons[week] = lessons; lessons[week] = lessonsList;
await store(); await store();
await convertBySettings(); await convertBySettings();
@ -78,7 +85,7 @@ class TimetableProvider with ChangeNotifier {
String userId = user.id; String userId = user.id;
// -TODO: clear indexes with weeks outside of the current school year // -TODO: clear indexes with weeks outside of the current school year
await _database.userStore.storeLessons(_lessons, userId: userId); await _database.userStore.storeLessons(lessons, userId: userId);
} }
// Future<void> setLessonCount(SubjectLessonCount lessonCount, {bool store = true}) async { // Future<void> setLessonCount(SubjectLessonCount lessonCount, {bool store = true}) async {

View File

@ -0,0 +1,41 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class BetaChip extends StatelessWidget {
const BetaChip({Key? key, this.disabled = false}) : super(key: key);
final bool disabled;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 25,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Center(
child: Text(
"BETA",
softWrap: true,
style: TextStyle(
fontSize: 10,
color: disabled
? AppColors.of(context).text.withOpacity(.5)
: Colors.white,
fontWeight: FontWeight.w600,
overflow: TextOverflow.ellipsis,
),
),
),
),
decoration: BoxDecoration(
color: !disabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
borderRadius: BorderRadius.circular(40),
),
),
);
}
}

View File

@ -75,6 +75,7 @@ class ProfileButton extends StatelessWidget {
snapSpec: const SnapSpec( snapSpec: const SnapSpec(
snap: true, snap: true,
snappings: [1.0], snappings: [1.0],
initialSnap: 1.0,
positioning: SnapPositioning.relativeToSheetHeight, positioning: SnapPositioning.relativeToSheetHeight,
), ),
cornerRadius: 16, cornerRadius: 16,

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ProgressBar extends StatelessWidget { class ProgressBar extends StatelessWidget {
const ProgressBar({Key? key, required this.value, this.backgroundColor}) : super(key: key); const ProgressBar(
{Key? key, required this.value, this.backgroundColor, this.height = 8.0})
: super(key: key);
final double value; final double value;
final Color? backgroundColor; final Color? backgroundColor;
final double height;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -13,11 +16,13 @@ class ProgressBar extends StatelessWidget {
// Background // Background
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.1) : Colors.white.withOpacity(0.1), color: Theme.of(context).brightness == Brightness.light
? Colors.black.withOpacity(0.1)
: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(45.0), borderRadius: BorderRadius.circular(45.0),
), ),
width: double.infinity, width: double.infinity,
height: 8.0, height: height,
), ),
// Slider // Slider
@ -26,8 +31,9 @@ class ProgressBar extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: CustomPaint( child: CustomPaint(
painter: ProgressPainter( painter: ProgressPainter(
backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.secondary, backgroundColor:
height: 8.0, backgroundColor ?? Theme.of(context).colorScheme.secondary,
height: height,
value: value.clamp(0, 1), value: value.clamp(0, 1),
), ),
), ),
@ -38,7 +44,10 @@ class ProgressBar extends StatelessWidget {
} }
class ProgressPainter extends CustomPainter { class ProgressPainter extends CustomPainter {
ProgressPainter({required this.height, required this.value, required this.backgroundColor}); ProgressPainter(
{required this.height,
required this.value,
required this.backgroundColor});
final double height; final double height;
final double value; final double value;
@ -64,6 +73,8 @@ class ProgressPainter extends CustomPainter {
@override @override
bool shouldRepaint(ProgressPainter oldDelegate) { bool shouldRepaint(ProgressPainter oldDelegate) {
return value != oldDelegate.value || height != oldDelegate.height || backgroundColor != oldDelegate.backgroundColor; return value != oldDelegate.value ||
height != oldDelegate.height ||
backgroundColor != oldDelegate.backgroundColor;
} }
} }

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class RoundBorderIcon extends StatelessWidget {
final Color color;
final double width;
final double padding;
final Widget icon;
const RoundBorderIcon(
{Key? key,
this.color = Colors.black,
this.width = 1.5,
this.padding = 5.0,
required this.icon})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: color, width: width),
borderRadius: BorderRadius.circular(50.0),
),
child: Padding(
padding: EdgeInsets.all(padding),
child: icon,
),
);
}
}

View File

@ -9,7 +9,9 @@ import 'package:provider/provider.dart';
import 'absence_tile.i18n.dart'; import 'absence_tile.i18n.dart';
class AbsenceTile extends StatelessWidget { class AbsenceTile extends StatelessWidget {
const AbsenceTile(this.absence, {Key? key, this.onTap, this.elevation = 0.0, this.padding}) : super(key: key); const AbsenceTile(this.absence,
{Key? key, this.onTap, this.elevation = 0.0, this.padding})
: super(key: key);
final Absence absence; final Absence absence;
final void Function()? onTap; final void Function()? onTap;
@ -37,39 +39,61 @@ class AbsenceTile extends StatelessWidget {
child: Material( child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: Padding( child: Padding(
padding: padding ?? (group ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 8.0)), padding: padding ??
(group
? EdgeInsets.zero
: const EdgeInsets.symmetric(
horizontal: 8.0,
)),
child: ListTile( child: ListTile(
onTap: onTap, onTap: onTap,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
dense: group, dense: group,
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0), contentPadding: const EdgeInsets.only(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)), left: 14.0, right: 12.0, top: 2.0, bottom: 2.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)),
leading: Container( leading: Container(
width: 44.0, width: 44.0,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: !group ? color.withOpacity(.25) : null, color: !group ? color.withOpacity(.25) : null,
), ),
child: Center(child: Icon(justificationIcon(absence.state), color: color)), child: Center(
child: Icon(justificationIcon(absence.state), color: color)),
), ),
title: !group title: !group
? Text.rich(TextSpan( ? Text.rich(TextSpan(
text: "${absence.delay == 0 ? "" : absence.delay}", text: "${absence.delay == 0 ? "" : absence.delay}",
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15.5), style: const TextStyle(
fontWeight: FontWeight.w700, fontSize: 15.5),
children: [ children: [
TextSpan( TextSpan(
text: absence.delay == 0 text: absence.delay == 0
? justificationName(absence.state).fill(["absence".i18n]).capital() ? justificationName(absence.state)
: 'minute'.plural(absence.delay) + justificationName(absence.state).fill(["delay".i18n]), .fill(["absence".i18n]).capital()
: 'minute'.plural(absence.delay) +
justificationName(absence.state)
.fill(["delay".i18n]),
style: const TextStyle(fontWeight: FontWeight.w600), style: const TextStyle(fontWeight: FontWeight.w600),
), ),
], ],
)) ))
: Text( : Text(
(absence.lessonIndex != null ? "${absence.lessonIndex}. " : "") + (absence.subject.renamedTo ?? absence.subject.name.capital()), (absence.lessonIndex != null
? "${absence.lessonIndex}. "
: "") +
(absence.subject.renamedTo ??
absence.subject.name.capital()),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14.0,
fontStyle: absence.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
), ),
subtitle: !group subtitle: !group
? Text( ? Text(
@ -77,7 +101,12 @@ class AbsenceTile extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date), // DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w500,
fontStyle: absence.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
) )
: null, : null,
), ),
@ -97,7 +126,8 @@ class AbsenceTile extends StatelessWidget {
} }
} }
static Color justificationColor(Justification state, {required BuildContext context}) { static Color justificationColor(Justification state,
{required BuildContext context}) {
switch (state) { switch (state) {
case Justification.excused: case Justification.excused:
return AppColors.of(context).green; return AppColors.of(context).green;

View File

@ -17,19 +17,23 @@ import 'package:provider/provider.dart';
import 'absence_view.i18n.dart'; import 'absence_view.i18n.dart';
class AbsenceView extends StatelessWidget { class AbsenceView extends StatelessWidget {
const AbsenceView(this.absence, {Key? key, this.outsideContext, this.viewable = false}) : super(key: key); const AbsenceView(this.absence,
{Key? key, this.outsideContext, this.viewable = false})
: super(key: key);
final Absence absence; final Absence absence;
final BuildContext? outsideContext; final BuildContext? outsideContext;
final bool viewable; final bool viewable;
static show(Absence absence, {required BuildContext context}) { static show(Absence absence, {required BuildContext context}) {
showBottomCard(context: context, child: AbsenceView(absence, outsideContext: context)); showBottomCard(
context: context, child: AbsenceView(absence, outsideContext: context));
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color color = AbsenceTile.justificationColor(absence.state, context: context); Color color =
AbsenceTile.justificationColor(absence.state, context: context);
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context); SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
return Padding( return Padding(
@ -41,7 +45,8 @@ class AbsenceView extends StatelessWidget {
ListTile( ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 16.0, right: 12.0), contentPadding: const EdgeInsets.only(left: 16.0, right: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
leading: Container( leading: Container(
width: 44.0, width: 44.0,
height: 44.0, height: 44.0,
@ -60,10 +65,18 @@ class AbsenceView extends StatelessWidget {
absence.subject.renamedTo ?? absence.subject.name.capital(), absence.subject.renamedTo ?? absence.subject.name.capital(),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w700, fontStyle: absence.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w700,
fontStyle: absence.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
), ),
subtitle: Text( subtitle: Text(
absence.teacher, (absence.teacher.isRenamed
? absence.teacher.renamedTo
: absence.teacher.name) ??
'',
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date), // DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
@ -77,12 +90,15 @@ class AbsenceView extends StatelessWidget {
if (absence.delay > 0) if (absence.delay > 0)
Detail( Detail(
title: "delay".i18n, title: "delay".i18n,
description: absence.delay.toString() + " " + "minutes".i18n.plural(absence.delay), description: absence.delay.toString() +
" " +
"minutes".i18n.plural(absence.delay),
), ),
if (absence.lessonIndex != null) if (absence.lessonIndex != null)
Detail( Detail(
title: "Lesson".i18n, title: "Lesson".i18n,
description: "${absence.lessonIndex}. (${absence.lessonStart.format(context, timeOnly: true)}" description:
"${absence.lessonIndex}. (${absence.lessonStart.format(context, timeOnly: true)}"
" - " " - "
"${absence.lessonEnd.format(context, timeOnly: true)})", "${absence.lessonEnd.format(context, timeOnly: true)})",
), ),
@ -91,13 +107,19 @@ class AbsenceView extends StatelessWidget {
title: "Excuse".i18n, title: "Excuse".i18n,
description: absence.justification?.description ?? "", description: absence.justification?.description ?? "",
), ),
if (absence.mode != null) Detail(title: "Mode".i18n, description: absence.mode?.description ?? ""), if (absence.mode != null)
Detail(title: "Submit date".i18n, description: absence.submitDate.format(context)), Detail(
title: "Mode".i18n,
description: absence.mode?.description ?? ""),
Detail(
title: "Submit date".i18n,
description: absence.submitDate.format(context)),
// Show in timetable // Show in timetable
if (!viewable) if (!viewable)
Padding( Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 6.0, top: 12.0), padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 6.0, top: 12.0),
child: PanelActionButton( child: PanelActionButton(
leading: const Icon(FeatherIcons.calendar), leading: const Icon(FeatherIcons.calendar),
title: Text( title: Text(
@ -109,12 +131,15 @@ class AbsenceView extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
if (outsideContext != null) { if (outsideContext != null) {
ReverseSearch.getLessonByAbsence(absence, context).then((lesson) { ReverseSearch.getLessonByAbsence(absence, context)
.then((lesson) {
if (lesson != null) { if (lesson != null) {
TimetablePage.jump(outsideContext!, lesson: lesson); TimetablePage.jump(outsideContext!, lesson: lesson);
} else { } else {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( ScaffoldMessenger.of(context)
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)), .showSnackBar(CustomSnackBar(
content: Text("Cannot find lesson".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red, backgroundColor: AppColors.of(context).red,
context: context, context: context,
)); ));

View File

@ -5,6 +5,8 @@ import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_c
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart'; import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
import 'package:filcnaplo/utils/format.dart'; import 'package:filcnaplo/utils/format.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:rounded_expansion_tile/rounded_expansion_tile.dart';
import 'absence_group_tile.i18n.dart'; import 'absence_group_tile.i18n.dart';
class AbsenceGroupTile extends StatelessWidget { class AbsenceGroupTile extends StatelessWidget {
@ -30,13 +32,17 @@ class AbsenceGroupTile extends StatelessWidget {
child: Material( child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: Padding( child: Padding(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0), padding: padding ??
const EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.0),
child: AbsenceGroupContainer( child: AbsenceGroupContainer(
child: ExpansionTile( child: RoundedExpansionTile(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)), borderRadius: BorderRadius.circular(10)),
tilePadding: const EdgeInsets.symmetric(horizontal: 8.0), childrenPadding: const EdgeInsets.symmetric(horizontal: 8.0),
backgroundColor: Colors.transparent, tileColor: Colors.transparent,
duration: const Duration(milliseconds: 250),
trailingDuration: 0.5,
trailing: const Icon(FeatherIcons.chevronDown),
leading: Container( leading: Container(
width: 44.0, width: 44.0,
height: 44.0, height: 44.0,

View File

@ -12,7 +12,8 @@ class ExamView extends StatelessWidget {
final Exam exam; final Exam exam;
static show(Exam exam, {required BuildContext context}) => showBottomCard(context: context, child: ExamView(exam)); static show(Exam exam, {required BuildContext context}) =>
showBottomCard(context: context, child: ExamView(exam));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,7 +28,8 @@ class ExamView extends StatelessWidget {
leading: Padding( leading: Padding(
padding: const EdgeInsets.only(left: 6.0), padding: const EdgeInsets.only(left: 6.0),
child: Icon( child: Icon(
SubjectIcon.resolveVariant(subjectName: exam.subjectName, context: context), SubjectIcon.resolveVariant(
subjectName: exam.subjectName, context: context),
size: 36.0, size: 36.0,
color: AppColors.of(context).text.withOpacity(.75), color: AppColors.of(context).text.withOpacity(.75),
), ),
@ -39,7 +41,10 @@ class ExamView extends StatelessWidget {
style: const TextStyle(fontWeight: FontWeight.w600), style: const TextStyle(fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
exam.teacher, (exam.teacher.isRenamed
? exam.teacher.renamedTo
: exam.teacher.name) ??
'',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
@ -51,9 +56,14 @@ class ExamView extends StatelessWidget {
), ),
// Details // Details
if (exam.writeDate.year != 0) Detail(title: "date".i18n, description: exam.writeDate.format(context)), if (exam.writeDate.year != 0)
if (exam.description != "") Detail(title: "description".i18n, description: exam.description), Detail(
if (exam.mode != null) Detail(title: "mode".i18n, description: exam.mode!.description), title: "date".i18n,
description: exam.writeDate.format(context)),
if (exam.description != "")
Detail(title: "description".i18n, description: exam.description),
if (exam.mode != null)
Detail(title: "mode".i18n, description: exam.mode!.description),
], ],
), ),
); );

View File

@ -11,7 +11,8 @@ import 'grade_view.i18n.dart';
class GradeView extends StatelessWidget { class GradeView extends StatelessWidget {
const GradeView(this.grade, {Key? key}) : super(key: key); const GradeView(this.grade, {Key? key}) : super(key: key);
static show(Grade grade, {required BuildContext context}) => showBottomCard(context: context, child: GradeView(grade)); static show(Grade grade, {required BuildContext context}) =>
showBottomCard(context: context, child: GradeView(grade));
final Grade grade; final Grade grade;
@ -30,10 +31,21 @@ class GradeView extends StatelessWidget {
grade.subject.renamedTo ?? grade.subject.name.capital(), grade.subject.renamedTo ?? grade.subject.name.capital(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: grade.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: grade.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
), ),
subtitle: Text( subtitle: Text(
!Provider.of<SettingsProvider>(context, listen: false).presentationMode ? grade.teacher : "Tanár", !Provider.of<SettingsProvider>(context, listen: false)
.presentationMode
? (grade.teacher.isRenamed
? grade.teacher.renamedTo
: grade.teacher.name) ??
''
: "Tanár",
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
@ -49,13 +61,20 @@ class GradeView extends StatelessWidget {
title: "value".i18n, title: "value".i18n,
description: "${grade.value.valueName} " + percentText(), description: "${grade.value.valueName} " + percentText(),
), ),
if (grade.description != "") Detail(title: "description".i18n, description: grade.description), if (grade.description != "")
if (grade.mode.description != "") Detail(title: "mode".i18n, description: grade.mode.description), Detail(title: "description".i18n, description: grade.description),
if (grade.writeDate.year != 0) Detail(title: "date".i18n, description: grade.writeDate.format(context)), if (grade.mode.description != "")
Detail(title: "mode".i18n, description: grade.mode.description),
if (grade.writeDate.year != 0)
Detail(
title: "date".i18n,
description: grade.writeDate.format(context)),
], ],
), ),
); );
} }
String percentText() => grade.value.weight != 100 && grade.value.weight > 0 ? "${grade.value.weight}%" : ""; String percentText() => grade.value.weight != 100 && grade.value.weight > 0
? "${grade.value.weight}%"
: "";
} }

View File

@ -51,10 +51,18 @@ class HomeworkView extends StatelessWidget {
homework.subject.renamedTo ?? homework.subject.name.capital(), homework.subject.renamedTo ?? homework.subject.name.capital(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: homework.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: homework.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
), ),
subtitle: Text( subtitle: Text(
homework.teacher, (homework.teacher.isRenamed
? homework.teacher.renamedTo
: homework.teacher.name) ??
'',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),

View File

@ -6,7 +6,8 @@ import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'changed_lesson_tile.i18n.dart'; import 'changed_lesson_tile.i18n.dart';
class ChangedLessonTile extends StatelessWidget { class ChangedLessonTile extends StatelessWidget {
const ChangedLessonTile(this.lesson, {Key? key, this.onTap, this.padding}) : super(key: key); const ChangedLessonTile(this.lesson, {Key? key, this.onTap, this.padding})
: super(key: key);
final Lesson lesson; final Lesson lesson;
final void Function()? onTap; final void Function()? onTap;
@ -21,7 +22,7 @@ class ChangedLessonTile extends StatelessWidget {
Color accent = Theme.of(context).colorScheme.secondary; Color accent = Theme.of(context).colorScheme.secondary;
if (lesson.substituteTeacher != "") { if (lesson.substituteTeacher?.name != '') {
accent = AppColors.of(context).yellow; accent = AppColors.of(context).yellow;
} }
@ -38,7 +39,8 @@ class ChangedLessonTile extends StatelessWidget {
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0), contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
onTap: onTap, onTap: onTap,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)), shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: SizedBox( leading: SizedBox(
width: 44.0, width: 44.0,
height: 44.0, height: 44.0,
@ -55,7 +57,9 @@ class ChangedLessonTile extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
lesson.substituteTeacher != "" ? "substituted".i18n : "cancelled".i18n, lesson.substituteTeacher?.name != ""
? "substituted".i18n
: "cancelled".i18n,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600), style: const TextStyle(fontWeight: FontWeight.w600),
@ -64,7 +68,9 @@ class ChangedLessonTile extends StatelessWidget {
lesson.subject.renamedTo ?? lesson.subject.name.capital(), lesson.subject.renamedTo ?? lesson.subject.name.capital(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w500,
fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
), ),
trailing: const Icon(FeatherIcons.arrowRight), trailing: const Icon(FeatherIcons.arrowRight),
minLeadingWidth: 0, minLeadingWidth: 0,

View File

@ -22,7 +22,7 @@ class LessonView extends StatelessWidget {
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = "."; if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
if (lesson.substituteTeacher != "") { if (lesson.substituteTeacher?.name != "") {
accent = AppColors.of(context).yellow; accent = AppColors.of(context).yellow;
} }
@ -54,10 +54,23 @@ class LessonView extends StatelessWidget {
lesson.subject.renamedTo ?? lesson.subject.name.capital(), lesson.subject.renamedTo ?? lesson.subject.name.capital(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: lesson.subject.isRenamed && settingsProvider.renamedSubjectsItalics ? FontStyle.italic : null), style: TextStyle(
fontWeight: FontWeight.w600,
fontStyle: lesson.subject.isRenamed &&
settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: null),
), ),
subtitle: Text( subtitle: Text(
lesson.substituteTeacher == "" ? lesson.teacher : lesson.substituteTeacher, ((lesson.substituteTeacher == null ||
lesson.substituteTeacher!.name == "")
? (lesson.teacher.isRenamed
? lesson.teacher.renamedTo
: lesson.teacher.name)
: (lesson.substituteTeacher!.isRenamed
? lesson.substituteTeacher!.renamedTo
: lesson.substituteTeacher!.name)) ??
'',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
@ -69,10 +82,18 @@ class LessonView extends StatelessWidget {
), ),
// Details // Details
if (lesson.room != "") Detail(title: "Room".i18n, description: lesson.room.replaceAll("_", " ")), if (lesson.room != "")
if (lesson.description != "") Detail(title: "Description".i18n, description: lesson.description), Detail(
if (lesson.lessonYearIndex != null) Detail(title: "Lesson Number".i18n, description: "${lesson.lessonYearIndex}."), title: "Room".i18n,
if (lesson.groupName != "") Detail(title: "Group".i18n, description: lesson.groupName), description: lesson.room.replaceAll("_", " ")),
if (lesson.description != "")
Detail(title: "Description".i18n, description: lesson.description),
if (lesson.lessonYearIndex != null)
Detail(
title: "Lesson Number".i18n,
description: "${lesson.lessonYearIndex}."),
if (lesson.groupName != "")
Detail(title: "Group".i18n, description: lesson.groupName),
], ],
), ),
); );

View File

@ -4,7 +4,8 @@ import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class NoteTile extends StatelessWidget { class NoteTile extends StatelessWidget {
const NoteTile(this.note, {Key? key, this.onTap, this.padding}) : super(key: key); const NoteTile(this.note, {Key? key, this.onTap, this.padding})
: super(key: key);
final Note note; final Note note;
final void Function()? onTap; final void Function()? onTap;
@ -20,11 +21,20 @@ class NoteTile extends StatelessWidget {
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0), contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
onTap: onTap, onTap: onTap,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)), shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
leading: ProfileImage( leading: ProfileImage(
name: note.teacher, name: (note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
radius: 22.0, radius: 22.0,
backgroundColor: ColorUtils.stringToColor(note.teacher), backgroundColor: ColorUtils.stringToColor(
(note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
),
), ),
title: Text( title: Text(
note.title, note.title,

View File

@ -12,7 +12,8 @@ class NoteView extends StatelessWidget {
final Note note; final Note note;
static void show(Note note, {required BuildContext context}) => showSlidingBottomSheet(context: context, child: NoteView(note)); static void show(Note note, {required BuildContext context}) =>
showSlidingBottomSheet(context: context, child: NoteView(note));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -25,9 +26,17 @@ class NoteView extends StatelessWidget {
// Header // Header
ListTile( ListTile(
leading: ProfileImage( leading: ProfileImage(
name: note.teacher, name: (note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
radius: 22.0, radius: 22.0,
backgroundColor: ColorUtils.stringToColor(note.teacher), backgroundColor: ColorUtils.stringToColor(
(note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
),
), ),
title: Text( title: Text(
note.title, note.title,
@ -36,7 +45,10 @@ class NoteView extends StatelessWidget {
style: const TextStyle(fontWeight: FontWeight.w600), style: const TextStyle(fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
note.teacher, (note.teacher.isRenamed
? note.teacher.renamedTo
: note.teacher.name) ??
'',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),

View File

@ -19,14 +19,18 @@ import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.i18n.dar
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AbsenceSubjectView extends StatelessWidget { class AbsenceSubjectView extends StatelessWidget {
const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []}) : super(key: key); const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []})
: super(key: key);
final Subject subject; final Subject subject;
final List<Absence> absences; final List<Absence> absences;
static void show(Subject subject, List<Absence> absences, {required BuildContext context}) { static void show(Subject subject, List<Absence> absences,
{required BuildContext context}) {
Navigator.of(context, rootNavigator: true) Navigator.of(context, rootNavigator: true)
.push<Absence>(CupertinoPageRoute(builder: (context) => AbsenceSubjectView(subject, absences: absences))) .push<Absence>(CupertinoPageRoute(
builder: (context) =>
AbsenceSubjectView(subject, absences: absences)))
.then((value) { .then((value) {
if (value == null) return; if (value == null) return;
@ -36,7 +40,8 @@ class AbsenceSubjectView extends StatelessWidget {
TimetablePage.jump(context, lesson: lesson); TimetablePage.jump(context, lesson: lesson);
} else { } else {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)), content: Text("Cannot find lesson".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red, backgroundColor: AppColors.of(context).red,
context: context, context: context,
)); ));
@ -54,7 +59,10 @@ class AbsenceSubjectView extends StatelessWidget {
date: a.date, date: a.date,
)) ))
.toList(); .toList();
List<Widget> absenceTiles = sortDateWidgets(context, dateWidgets: dateWidgets, padding: EdgeInsets.zero, hasShadow: true); List<Widget> absenceTiles = sortDateWidgets(context,
dateWidgets: dateWidgets,
padding: const EdgeInsets.symmetric(vertical: 6.0),
hasShadow: true);
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context); SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);

View File

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:filcnaplo_kreta_api/models/category.dart'; import 'package:filcnaplo_kreta_api/models/category.dart';
import 'package:filcnaplo_kreta_api/models/grade.dart'; import 'package:filcnaplo_kreta_api/models/grade.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart'; import 'package:filcnaplo_kreta_api/models/subject.dart';
import 'package:filcnaplo_kreta_api/models/teacher.dart';
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart'; import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
import 'package:filcnaplo_mobile_ui/common/material_action_button.dart'; import 'package:filcnaplo_mobile_ui/common/material_action_button.dart';
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart'; import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
@ -42,7 +43,8 @@ class _GradeCalculatorState extends State<GradeCalculator> {
padding: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.only(bottom: 8.0),
child: Text( child: Text(
"Grade Calculator".i18n, "Grade Calculator".i18n,
style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600), style:
const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600),
), ),
), ),
@ -63,7 +65,9 @@ class _GradeCalculatorState extends State<GradeCalculator> {
Container( Container(
width: 80.0, width: 80.0,
padding: const EdgeInsets.only(right: 12.0), padding: const EdgeInsets.only(right: 12.0),
child: Center(child: GradeValueWidget(GradeValue(newValue.toInt(), "", "", 0))), child: Center(
child: GradeValueWidget(
GradeValue(newValue.toInt(), "", "", 0))),
), ),
]), ]),
@ -90,7 +94,8 @@ class _GradeCalculatorState extends State<GradeCalculator> {
child: Center( child: Center(
child: TextField( child: TextField(
controller: _weightController, controller: _weightController,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 22.0), style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 22.0),
autocorrect: false, autocorrect: false,
textAlign: TextAlign.right, textAlign: TextAlign.right,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
@ -121,8 +126,9 @@ class _GradeCalculatorState extends State<GradeCalculator> {
child: MaterialActionButton( child: MaterialActionButton(
child: Text("Add Grade".i18n), child: Text("Add Grade".i18n),
onPressed: () { onPressed: () {
if (calculatorProvider.ghosts.length >= 30) { if (calculatorProvider.ghosts.length >= 50) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(content: Text("limit_reached".i18n), context: context)); ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("limit_reached".i18n), context: context));
return; return;
} }
@ -133,7 +139,11 @@ class _GradeCalculatorState extends State<GradeCalculator> {
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate)); grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
date = grades.first.date.add(const Duration(days: 7)); date = grades.first.date.add(const Duration(days: 7));
} else { } else {
List<Grade> grades = calculatorProvider.grades.where((e) => e.type == GradeType.midYear && e.subject == widget.subject).toList(); List<Grade> grades = calculatorProvider.grades
.where((e) =>
e.type == GradeType.midYear &&
e.subject == widget.subject)
.toList();
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate)); grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
date = grades.first.date; date = grades.first.date;
} }
@ -143,8 +153,9 @@ class _GradeCalculatorState extends State<GradeCalculator> {
date: date, date: date,
writeDate: date, writeDate: date,
description: "Ghost Grade".i18n, description: "Ghost Grade".i18n,
value: GradeValue(newValue.toInt(), "", "", newWeight.toInt()), value:
teacher: "Ghost", GradeValue(newValue.toInt(), "", "", newWeight.toInt()),
teacher: Teacher.fromString("Ghost"),
type: GradeType.ghost, type: GradeType.ghost,
form: "", form: "",
subject: widget.subject, subject: widget.subject,

View File

@ -1,6 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/utils/format.dart'; import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
@ -21,6 +23,11 @@ import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_pro
import 'package:filcnaplo_mobile_ui/pages/grades/grades_count.dart'; import 'package:filcnaplo_mobile_ui/pages/grades/grades_count.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/graph.dart'; import 'package:filcnaplo_mobile_ui/pages/grades/graph.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart'; import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_planner_screen.dart';
import 'package:filcnaplo_premium/models/premium_scopes.dart';
import 'package:filcnaplo_premium/providers/premium_provider.dart';
import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_state_screen.dart';
import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
@ -58,12 +65,16 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
late GradeProvider gradeProvider; late GradeProvider gradeProvider;
late GradeCalculatorProvider calculatorProvider; late GradeCalculatorProvider calculatorProvider;
late SettingsProvider settingsProvider; late SettingsProvider settingsProvider;
late DatabaseProvider dbProvider;
late UserProvider user;
late double average; late double average;
late Widget gradeGraph; late Widget gradeGraph;
bool gradeCalcMode = false; bool gradeCalcMode = false;
String plan = '';
List<Grade> getSubjectGrades(Subject subject) => !gradeCalcMode List<Grade> getSubjectGrades(Subject subject) => !gradeCalcMode
? gradeProvider.grades.where((e) => e.subject == subject).toList() ? gradeProvider.grades.where((e) => e.subject == subject).toList()
: calculatorProvider.grades.where((e) => e.subject == subject).toList(); : calculatorProvider.grades.where((e) => e.subject == subject).toList();
@ -147,6 +158,20 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
gradeTiles = List.castFrom(tiles); gradeTiles = List.castFrom(tiles);
} }
@override
void initState() {
super.initState();
user = Provider.of<UserProvider>(context, listen: false);
dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
}
void fetchGoalPlans() async {
plan = (await dbProvider.userQuery
.subjectGoalPlans(userId: user.id!))[widget.subject.id] ??
'';
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
gradeProvider = Provider.of<GradeProvider>(context); gradeProvider = Provider.of<GradeProvider>(context);
@ -192,6 +217,8 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
buildTiles(ghostGrades); buildTiles(ghostGrades);
} }
fetchGoalPlans();
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
floatingActionButtonLocation: ExpandableFab.location, floatingActionButtonLocation: ExpandableFab.location,
@ -209,31 +236,33 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
), ),
children: [ children: [
FloatingActionButton.small( FloatingActionButton.small(
heroTag: "btn_ghost_grades",
child: const Icon(FeatherIcons.plus), child: const Icon(FeatherIcons.plus),
backgroundColor: Theme.of(context).colorScheme.secondary, backgroundColor: Theme.of(context).colorScheme.secondary,
onPressed: () { onPressed: () {
gradeCalc(context); gradeCalc(context);
}, },
), ),
// FloatingActionButton.small( FloatingActionButton.small(
// child: const Icon(FeatherIcons.flag, size: 20.0), heroTag: "btn_goal_planner",
// backgroundColor: Theme.of(context).colorScheme.secondary, child: const Icon(FeatherIcons.flag, size: 20.0),
// onPressed: () { backgroundColor: Theme.of(context).colorScheme.secondary,
// if (!Provider.of<PremiumProvider>(context, listen: false) onPressed: () {
// .hasScope(PremiumScopes.goalPlanner)) { if (!Provider.of<PremiumProvider>(context, listen: false)
// PremiumLockedFeatureUpsell.show( .hasScope(PremiumScopes.goalPlanner)) {
// context: context, feature: PremiumFeature.goalplanner); PremiumLockedFeatureUpsell.show(
// return; context: context, feature: PremiumFeature.goalplanner);
// } return;
}
// ScaffoldMessenger.of(context).showSnackBar( // ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text("Hamarosan..."))); // const SnackBar(content: Text("Hamarosan...")));
// Navigator.of(context).push(CupertinoPageRoute( Navigator.of(context).push(CupertinoPageRoute(
// builder: (context) => PremiumGoalplannerNewGoalScreen( builder: (context) =>
// subject: widget.subject))); GoalPlannerScreen(subject: widget.subject)));
// }, },
// ), ),
], ],
), ),
), ),
@ -257,13 +286,42 @@ class _GradeSubjectViewState extends State<GradeSubjectView> {
const SizedBox(width: 6.0), const SizedBox(width: 6.0),
if (average != 0) if (average != 0)
Center(child: AverageDisplay(average: average)), Center(child: AverageDisplay(average: average)),
const SizedBox(width: 6.0),
if (plan != '')
Center(
child: GestureDetector(
onTap: () {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) =>
GoalStateScreen(subject: widget.subject)));
},
child: Container(
width: 54.0,
padding: const EdgeInsets.symmetric(vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(45.0),
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(.15),
),
child: Icon(
FeatherIcons.flag,
size: 17.0,
weight: 2.5,
color: Theme.of(context).colorScheme.primary,
),
),
),
),
const SizedBox(width: 12.0), const SizedBox(width: 12.0),
], ],
icon: SubjectIcon.resolveVariant( icon: SubjectIcon.resolveVariant(
subject: widget.subject, context: context), subject: widget.subject, context: context),
scrollController: _scrollController, scrollController: _scrollController,
title: widget.subject.renamedTo ?? widget.subject.name.capital(), title: widget.subject.renamedTo ?? widget.subject.name.capital(),
italic: settingsProvider.renamedSubjectsItalics && widget.subject.isRenamed, italic: settingsProvider.renamedSubjectsItalics &&
widget.subject.isRenamed,
child: SubjectGradesContainer( child: SubjectGradesContainer(
child: CupertinoScrollbar( child: CupertinoScrollbar(
child: ListView.builder( child: ListView.builder(

View File

@ -356,14 +356,14 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
// confetti 🎊 // confetti 🎊
if (_confettiController != null) if (_confettiController != null)
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.topCenter,
child: ConfettiWidget( child: ConfettiWidget(
confettiController: _confettiController!, confettiController: _confettiController!,
blastDirection: -pi / 2, blastDirectionality: BlastDirectionality.explosive,
emissionFrequency: 0.01, emissionFrequency: 0.02,
numberOfParticles: 80, numberOfParticles: 120,
maxBlastForce: 100, maxBlastForce: 20,
minBlastForce: 90, minBlastForce: 10,
gravity: 0.3, gravity: 0.3,
minimumSize: const Size(5, 5), minimumSize: const Size(5, 5),
maximumSize: const Size(20, 20), maximumSize: const Size(20, 20),

View File

@ -3,6 +3,7 @@ import 'package:filcnaplo/helpers/quick_actions.dart';
import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/observer.dart'; import 'package:filcnaplo/theme/observer.dart';
import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart'; import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/nabar.dart'; import 'package:filcnaplo_mobile_ui/screens/navigation/nabar.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart'; import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
@ -12,6 +13,7 @@ import 'package:filcnaplo/icons/filc_icons.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/status_bar.dart'; import 'package:filcnaplo_mobile_ui/screens/navigation/status_bar.dart';
import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart'; import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart';
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart'; import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
import 'package:filcnaplo_premium/ui/mobile/goal_planner/goal_complete_modal.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -23,6 +25,7 @@ import 'package:filcnaplo/api/providers/sync.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';
import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart'; import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart';
import 'package:background_fetch/background_fetch.dart'; import 'package:background_fetch/background_fetch.dart';
import 'package:filcnaplo_premium/providers/goal_provider.dart';
class NavigationScreen extends StatefulWidget { class NavigationScreen extends StatefulWidget {
const NavigationScreen({Key? key}) : super(key: key); const NavigationScreen({Key? key}) : super(key: key);
@ -42,7 +45,9 @@ class NavigationScreenState extends State<NavigationScreen>
late SettingsProvider settings; late SettingsProvider settings;
late NewsProvider newsProvider; late NewsProvider newsProvider;
late GoalProvider goalProvider;
late UpdateProvider updateProvider; late UpdateProvider updateProvider;
late GradeProvider gradeProvicer;
NavigatorState? get navigator => _navigatorState.currentState; NavigatorState? get navigator => _navigatorState.currentState;
@ -82,6 +87,7 @@ class NavigationScreenState extends State<NavigationScreen>
snapSpec: const SnapSpec( snapSpec: const SnapSpec(
snap: true, snap: true,
snappings: [1.0], snappings: [1.0],
initialSnap: 1.0,
positioning: SnapPositioning.relativeToSheetHeight, positioning: SnapPositioning.relativeToSheetHeight,
), ),
cornerRadius: 16, cornerRadius: 16,
@ -139,7 +145,6 @@ class NavigationScreenState extends State<NavigationScreen>
void initState() { void initState() {
super.initState(); super.initState();
HomeWidget.setAppGroupId('hu.refilc.naplo.group'); HomeWidget.setAppGroupId('hu.refilc.naplo.group');
_checkForWidgetLaunch(); _checkForWidgetLaunch();
@ -156,15 +161,22 @@ class NavigationScreenState extends State<NavigationScreen>
Provider.of<KretaClient>(context, listen: false).userAgent = Provider.of<KretaClient>(context, listen: false).userAgent =
settings.config.userAgent; settings.config.userAgent;
// Get news // get news
newsProvider = Provider.of<NewsProvider>(context, listen: false); newsProvider = Provider.of<NewsProvider>(context, listen: false);
newsProvider.restore().then((value) => newsProvider.fetch()); newsProvider.restore().then((value) => newsProvider.fetch());
// Get releases // init grade provider (for goals)
gradeProvicer = Provider.of<GradeProvider>(context, listen: false);
// get goals
goalProvider = Provider.of<GoalProvider>(context, listen: false);
goalProvider.fetchDone(gradeProvider: gradeProvicer);
// get releases
updateProvider = Provider.of<UpdateProvider>(context, listen: false); updateProvider = Provider.of<UpdateProvider>(context, listen: false);
updateProvider.fetch(); updateProvider.fetch();
// Initial sync // initial sync
syncAll(context); syncAll(context);
setupQuickActions(); setupQuickActions();
} }
@ -195,13 +207,19 @@ class NavigationScreenState extends State<NavigationScreen>
setSystemChrome(context); setSystemChrome(context);
settings = Provider.of<SettingsProvider>(context); settings = Provider.of<SettingsProvider>(context);
newsProvider = Provider.of<NewsProvider>(context); newsProvider = Provider.of<NewsProvider>(context);
goalProvider = Provider.of<GoalProvider>(context);
// Show news // show news and complete goals
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (newsProvider.show) { if (newsProvider.show) {
newsProvider.lock(); NewsView.show(newsProvider.news[0], context: context)
NewsView.show(newsProvider.news[newsProvider.state], context: context)
.then((value) => newsProvider.release()); .then((value) => newsProvider.release());
newsProvider.lock();
}
if (goalProvider.hasDoneGoals) {
GoalCompleteModal.show(goalProvider.doneSubject!, context: context);
goalProvider.lock();
} }
}); });

View File

@ -31,23 +31,30 @@ class NewsView extends StatelessWidget {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0, right: 6.0, top: 14.0, bottom: 8.0), padding: const EdgeInsets.only(
left: 8.0, right: 6.0, top: 14.0, bottom: 8.0),
child: Text( child: Text(
news.title, news.title,
maxLines: 3, maxLines: 3,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18.0), style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 18.0),
), ),
), ),
SelectableLinkify( SelectableLinkify(
text: news.content.escapeHtml(), text: news.content.escapeHtml(),
options: const LinkifyOptions(looseUrl: true, removeWww: true), options:
const LinkifyOptions(looseUrl: true, removeWww: true),
onOpen: (link) { onOpen: (link) {
launch( launch(
link.url, link.url,
customTabsOption: CustomTabsOption(showPageTitle: true, toolbarColor: Theme.of(context).scaffoldBackgroundColor), customTabsOption: CustomTabsOption(
showPageTitle: true,
toolbarColor:
Theme.of(context).scaffoldBackgroundColor),
); );
}, },
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0), style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 14.0),
), ),
], ],
), ),
@ -59,10 +66,15 @@ class NewsView extends StatelessWidget {
children: [ children: [
if (news.link != "") if (news.link != "")
DialogButton( DialogButton(
label: news.openLabel != "" ? news.openLabel : "open".i18n.toUpperCase(), label: news.openLabel != ""
? news.openLabel
: "open".i18n.toUpperCase(),
onTap: () => launch( onTap: () => launch(
news.link, news.link,
customTabsOption: CustomTabsOption(showPageTitle: true, toolbarColor: Theme.of(context).scaffoldBackgroundColor), customTabsOption: CustomTabsOption(
showPageTitle: true,
toolbarColor:
Theme.of(context).scaffoldBackgroundColor),
), ),
), ),
DialogButton( DialogButton(
@ -78,12 +90,15 @@ class NewsView extends StatelessWidget {
); );
} }
static Future<T?> show<T>(News news, {required BuildContext context, bool force = false}) { static Future<T?> show<T>(News news,
{required BuildContext context, bool force = false}) {
if (news.title == "") return Future<T?>.value(null); if (news.title == "") return Future<T?>.value(null);
bool popup = news.platform == '' || force; bool popup = news.platform == 'all' || force;
if (Provider.of<SettingsProvider>(context, listen: false).newsEnabled || news.emergency || force) { if (Provider.of<SettingsProvider>(context, listen: false).newsEnabled ||
news.emergency ||
force) {
switch (news.platform.trim().toLowerCase()) { switch (news.platform.trim().toLowerCase()) {
case "android": case "android":
if (Platform.isAndroid) popup = true; if (Platform.isAndroid) popup = true;
@ -100,6 +115,9 @@ class NewsView extends StatelessWidget {
case "macos": case "macos":
if (Platform.isMacOS) popup = true; if (Platform.isMacOS) popup = true;
break; break;
case "all":
popup = true;
break;
default: default:
popup = true; popup = true;
} }
@ -108,7 +126,10 @@ class NewsView extends StatelessWidget {
} }
if (popup) { if (popup) {
return showDialog<T?>(context: context, builder: (context) => NewsView(news), barrierDismissible: true); return showDialog<T?>(
context: context,
builder: (context) => NewsView(news),
barrierDismissible: true);
} else { } else {
return Future<T?>.value(null); return Future<T?>.value(null);
} }

View File

@ -0,0 +1,226 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo_mobile_ui/common/beta_chip.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'notifications_screen.i18n.dart';
class MenuNotifications extends StatelessWidget {
const MenuNotifications({Key? key, required this.settings}) : super(key: key);
final SettingsProvider settings;
@override
Widget build(BuildContext context) {
return PanelButton(
padding: const EdgeInsets.only(left: 14.0),
onPressed: () {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(builder: (context) => const NotificationsScreen()),
);
},
title: Row(
children: [
Text(
"notifications_screen".i18n,
style: TextStyle(
color: AppColors.of(context)
.text
.withOpacity(settings.notificationsEnabled ? 1.0 : .5)),
),
const SizedBox(width: 5.0),
BetaChip(
disabled: !settings.notificationsEnabled,
),
],
),
leading: settings.notificationsEnabled
? const Icon(FeatherIcons.messageSquare)
: Icon(FeatherIcons.messageSquare,
color: AppColors.of(context).text.withOpacity(.25)),
trailingDivider: true,
trailing: Switch(
onChanged: (v) async {
settings.update(notificationsEnabled: v);
},
value: settings.notificationsEnabled,
activeColor: Theme.of(context).colorScheme.secondary,
),
);
}
}
class NotificationsScreen extends StatelessWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context) {
SettingsProvider settings = Provider.of<SettingsProvider>(context);
return Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"notifications_screen".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Panel(
child: Column(
children: [
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsGradesEnabled,
onChanged: (v) =>
settings.update(notificationsGradesEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.bookmark,
color: settings.notificationsGradesEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"grades".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsGradesEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsAbsencesEnabled,
onChanged: (v) =>
settings.update(notificationsAbsencesEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.clock,
color: settings.notificationsAbsencesEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"absences".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsAbsencesEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsMessagesEnabled,
onChanged: (v) =>
settings.update(notificationsMessagesEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.messageSquare,
color: settings.notificationsMessagesEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"messages".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsMessagesEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsLessonsEnabled,
onChanged: (v) =>
settings.update(notificationsLessonsEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.calendar,
color: settings.notificationsLessonsEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"lessons".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsLessonsEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"notifications_screen": "Notifications",
"grades": "Grades",
"absences": "Absences",
"messages": "Messages",
"lessons": "Lessons"
},
"hu_hu": {
"notifications_screen": "Értesítések",
"grades": "Jegyek",
"absences": "Hiányzások",
"messages": "Üzenetek",
"lessons": "Órák"
},
"de_de": {
"notifications_screen": "Mitteilung",
"grades": "Noten",
"absences": "Fehlen",
"messages": "Nachrichten",
"lessons": "Unterricht"
},
};
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);
}

View File

@ -28,27 +28,22 @@ class PrivacyView extends StatelessWidget {
), ),
SelectableLinkify( SelectableLinkify(
text: """ text: """
A reFilc egy kliensalkalmazás, segítségével az e-Kréta rendszeréből letöltheted és felhasználóbarát módon megjelenítheted az adataidat. A reFilc (továbbiakban alkalmazás) egy mobilos, asztali és webes kliensalkalmazás, segítségével az e-Kréta rendszeréből letöltheted és felhasználóbarát módon megjelenítheted az adataidat. Tanulmányi adataid csak közvetlenül az alkalmazás és a Kréta-szerverek között közlekednek, titkosított kapcsolaton keresztül.
Tanulmányi adataid csak közvetlenül az alkalmazás és a Kréta-szerverek között közlekednek, titkosított kapcsolaton keresztül.
A reFilc fejlesztői és üzemeltetői a tanulmányi adataidat semmilyen célból nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az e-Kréta Informatikai Zrt. kezeli, az ő tájékoztatójukat itt találod: https://tudasbazis.ekreta.hu/pages/viewpage.action?pageId=4065038. A reFilc fejlesztői és/vagy üzemeltetői, valamint az alkalmazás a tanulmányi és személyes adataidat semmilyen célból és semmilyen körülmények között nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az Educational Development Informatikai Zrt. kezeli, az Ő adatkezeléssel kapcsolatos tájékoztatójukat itt találod: https://tudasbazis.ekreta.hu/pages/viewpage.action?pageId=4065038
Azok törlésével vagy módosítával kapcsolatban keresd az osztályfőnöködet vagy az iskolád rendszergazdáját.
Az alkalmazás névtelen használati statisztikákat gyűjt, ezek alapján tudjuk meghatározni a felhasználók és a telepítések számát. Ezt a beállításokban kikapcsolhatod. Azok törlésével vagy módosítával kapcsolatban keresd az osztályfőnöködet vagy az iskolád rendszergazdáját.
Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva.
Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére. Az alkalmazás névtelen használati statisztikákat gyűjt, ezek alapján tudjuk meghatározni a felhasználók és a telepítések számát, valamint az eszközük platformját. Ezt a beállításokban kikapcsolhatod. Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva, hogy pontosabb információnk legyen a felhasználóink platform-megoszlásáról.
Ez személyes- vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibáról és eszközödről.
A küldés előtt megjelenő képernyőn a te felelősséged átnézni a továbbításra kerülő adatsort.
A hibajelentéseket a reFilc fejlesztői felületén és egy privát Discord szobában tároljuk, ezekhez csak az app fejlesztői férnek hozzá.
Az alkalmazás belépéskor a GitHub API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen is tölti le a telepítőt.
Ha az adataiddal kapcsolatban bármilyen kérdésed van (törlés, módosítás, adathordozás), keress minket a filcnaplo@filcnaplo.hu címen. Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére. Ez személyes- és/vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibáról, annak okáról és eszközödről. A küldés előtt megjelenő képernyőn a te felelősséged átnézni a továbbításra kerülő adatsort. A hibajelentéseket a reFilc fejlesztői felületén és egy privát Discord szobában tároljuk, ezekhez csak az app fejlesztői férnek hozzá.
Az alkalmazás használatával jelzed, hogy ezt a tájékoztatót tudomásul vetted. Az alkalmazás (az alábbi platformokon: Android, Linux, Windows) minden egyes indításakor a reFilc API, valamint a Github API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen letölti és telepíti a frissítést.
Utolsó módosítás: 2023. 05. 28. Amennyiben az adataiddal kapcsolatban bármilyen kérdésed van (megtekintés, törlés, módosítás, adathordozás), keress minket a social@refilc.hu e-mail címen, vagy Discord szerverünkön!
""",
A kliensalkalmazás bármely eszközön és platformon történő használatával tudomásul vetted és elfogadod a jelen adatkezelési tájékoztatót. A reFilc csapata fenntartja a jogot a tájékoztató módosítására és a módosításokról nem köteles értesíteni a felhasználóit!
""",
onOpen: (link) => launch(link.url, onOpen: (link) => launch(link.url,
customTabsOption: CustomTabsOption( customTabsOption: CustomTabsOption(
toolbarColor: Theme.of(context).scaffoldBackgroundColor, toolbarColor: Theme.of(context).scaffoldBackgroundColor,

Some files were not shown because too many files have changed in this diff Show More