Compare commits

...

48 Commits
3.1.1 ... 3.2.3

Author SHA1 Message Date
unknown
bf8ca49f98 fix build script 2022-02-01 18:37:34 +01:00
unknown
0fca636311 compile time version definition 2022-02-01 18:33:13 +01:00
Brúnó Salomon
8673a9e42a chore: version + changelog 2022-01-22 21:55:49 +01:00
Brúnó Salomon
0390d0df39 feat(quick-actions): add localization 2022-01-17 16:50:32 +01:00
Brúnó Salomon
4ec2f74fee chore(quick-actions): add ios icons 2022-01-17 16:45:55 +01:00
ezyyeah
e2078db34b Merge branch 'master' of https://github.com/filc/naplo 2022-01-16 23:08:29 +01:00
ezyyeah
1f39bdc301 quick actions 2022-01-16 23:08:16 +01:00
unknown
029e841d7b version bump 2022-01-16 21:26:55 +01:00
unknown
6a16f93884 version bump 2022-01-16 21:07:08 +01:00
unknown
cfc0229f09 changelog 2022-01-16 21:03:45 +01:00
unknown
a68dd759d8 homework attachments 2022-01-16 18:15:26 +01:00
unknown
e7d0e3805d Merge branch 'master' of ssh://github.com/filc/naplo 2022-01-16 14:08:07 +01:00
unknown
d617d9ef47 changelog 2022-01-16 14:07:05 +01:00
Unknown
2564224a6e Update changelog.md 2022-01-15 01:52:03 +01:00
unknown
5c2b690bc8 mobile 2022-01-15 01:50:42 +01:00
unknown
ff72d146c0 version bump 2022-01-14 23:50:11 +01:00
unknown
088b6e4580 mobile 2022-01-14 23:44:47 +01:00
Unknown
66793607e8 Update README.md 2022-01-14 23:19:04 +01:00
unknown
c91e792c6c changelog 2022-01-14 23:09:50 +01:00
unknown
16d0bd4163 mobile 2022-01-14 23:09:38 +01:00
Unknown
e559338483 Update README.md 2022-01-08 18:28:47 +01:00
unknown
f2c29aa81b 3.2.0 2022-01-08 15:42:49 +01:00
unknown
e44a3b7330 mobile 2022-01-07 07:05:54 +01:00
unknown
64606311bf mobile 2022-01-07 06:49:17 +01:00
unknown
43c4d2e454 mobile 2022-01-05 16:11:10 +01:00
unknown
9b579ad196 cleanup 2022-01-05 16:10:21 +01:00
unknown
425a4aaa91 cleanup 2022-01-05 15:39:24 +01:00
unknown
506c04aea9 build fix 2022-01-05 13:47:53 +01:00
unknown
cd18cfb220 mobile 2022-01-05 13:03:02 +01:00
unknown
f071e59e7a kreten 2022-01-05 13:02:57 +01:00
unknown
49f3a447b3 desktop 2022-01-05 13:02:52 +01:00
unknown
339dbea1ef flutter linting 2022-01-05 13:02:22 +01:00
unknown
79f6ef4c50 kreta 2022-01-05 12:32:26 +01:00
unknown
ae18fbab2e mobile 2022-01-05 12:32:18 +01:00
unknown
9003e13aa1 updater update 2022-01-05 12:30:43 +01:00
unknown
e58f4859b4 changelog 2022-01-05 12:30:07 +01:00
unknown
be809c6aaf Merge branch 'master' of ssh://github.com/filc/naplo 2022-01-05 12:18:48 +01:00
Brúnó Salomon
a6b012035b mobile 2021-12-30 17:26:43 +01:00
unknown
a0ef512c55 changelog 2021-12-03 23:02:12 +01:00
unknown
4e1c1be6e4 better updates 2021-12-03 23:00:13 +01:00
unknown
92a93941ab changelog 2021-12-03 22:08:27 +01:00
Unknown
ece621455c Update README.md 2021-11-20 15:55:44 +01:00
Unknown
0787f97a6f Update README.md 2021-11-17 22:52:29 +01:00
unknown
c74975e3bc kreten 2021-11-07 11:26:29 +01:00
unknown
4b669b0069 mobile 2021-11-07 11:26:21 +01:00
unknown
d24286b61c mobile 2021-11-07 10:35:57 +01:00
unknown
40f3d2159a revert build fixes, downgrade permission_handler (sdk 30) 2021-11-02 22:10:35 +01:00
unknown
8df77b5c06 3.1.1 changelog 2021-11-02 21:30:49 +01:00
56 changed files with 470 additions and 199 deletions

4
.vscode/launch.json vendored
View File

@@ -7,8 +7,8 @@
{ {
"name": "filcnaplo", "name": "filcnaplo",
"cwd": "filcnaplo", "cwd": "filcnaplo",
"request": "launch", "request": "attach",
"type": "dart" "type": "dart",
} }
] ]
} }

View File

@@ -9,16 +9,16 @@
## Setup ## Setup
### Clone the project: ### Clone the project
``` ```sh
$ git clone --recursive https://github.com/filc/naplo git clone --recursive https://github.com/filc/naplo
$ cd naplo cd naplo
``` ```
### Run the app: ### Run the app
``` ```sh
$ cd filcnaplo cd filcnaplo
$ flutter run flutter run
``` ```

View File

@@ -1,8 +1,6 @@
- Elmaradt és helyettesített órák (főoldal) - Órarendben dolgozatok javítása
- Feljegyzések (főoldal) - Kilógó félév jelző javítása a grafikonon
- Faliújság (főoldal) - Dicséretes jegyek jelzése
- Házi feladatok (főoldal) - Üzenet & Szellem jegy animációk
- Dolgozatok (főoldal) - Design javítások
- Állapot jelző a képernyő alján - Kisebb hibajavítások
- Hét napjai dátumoknál (Például ha okt. 3. van és kaptál egy jegyet okt. 1-én akkor azt fogja kiírni, hogy Péntek és nem a hónapot, napot)
- Hibajavítások

View File

@@ -8,7 +8,6 @@
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
build.sh
# IntelliJ related # IntelliJ related
*.iml *.iml

View File

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

View File

@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="hu.filc.naplo"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="hu.filc.naplo">
<application android:label="Filc Napló" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"> <application android:label="Filc Napló" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
<activity android:exported="false" android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <activity android:exported="true" android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.6.10'
ext { ext {
compileSdkVersion = 31 compileSdkVersion = 31
@@ -13,7 +13,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.2' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

19
filcnaplo/build.sh Normal file
View File

@@ -0,0 +1,19 @@
#!/bin/fish
# With build number
function get_version_bn
cat pubspec.yaml | grep version: | cut -d' ' -f2
end
function get_version
cat pubspec.yaml | grep version: | cut -d' ' -f2 | cut -d+ -f1
end
if test -e /mnt/enc/keys/filc3.properties
set -x ANDROID_SIGNING /mnt/enc/keys/filc3.properties
end
flutter build apk --release --dart-define=APPVER=(get_version)
cp -v "build/app/outputs/flutter-apk/app-release.apk" ~/"Desktop/hu.filc.naplo_"(get_version_bn).apk
notify-send "Flutter" "Apk build done."

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

View File

@@ -1,4 +1,7 @@
// ignore_for_file: avoid_print
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:filcnaplo/models/config.dart'; import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/models/news.dart'; import 'package:filcnaplo/models/news.dart';
@@ -11,23 +14,23 @@ import 'package:connectivity_plus/connectivity_plus.dart';
class FilcAPI { class FilcAPI {
// Public API // Public API
static const SCHOOL_LIST = "https://filcnaplo.hu/v2/school_list.json"; static const schoolList = "https://filcnaplo.hu/v2/school_list.json";
static const NEWS = "https://filcnaplo.hu/v2/news.json"; static const news = "https://filcnaplo.hu/v2/news.json";
static const SUPPORTERS = "https://filcnaplo.hu/v2/supporters.json"; static const supporters = "https://filcnaplo.hu/v2/supporters.json";
// Private API // Private API
static const CONFIG = "https://api.filcnaplo.hu/config"; static const config = "https://api.filcnaplo.hu/config";
static const REPORT = "https://api.filcnaplo.hu/report"; static const reportApi = "https://api.filcnaplo.hu/report";
// Updates // Updates
static const REPO = "filc/naplo"; static const repo = "filc/naplo";
static const RELEASES = "https://api.github.com/repos/$REPO/releases"; static const releases = "https://api.github.com/repos/$repo/releases";
static Future<bool> checkConnectivity() async => (await Connectivity().checkConnectivity()) != ConnectivityResult.none; static Future<bool> checkConnectivity() async => (await Connectivity().checkConnectivity()) != ConnectivityResult.none;
static Future<List<School>?> getSchools() async { static Future<List<School>?> getSchools() async {
try { try {
http.Response res = await http.get(Uri.parse(SCHOOL_LIST)); http.Response res = await http.get(Uri.parse(schoolList));
if (res.statusCode == 200) { if (res.statusCode == 200) {
List<School> schools = (jsonDecode(res.body) as List).cast<Map>().map((json) => School.fromJson(json)).toList(); List<School> schools = (jsonDecode(res.body) as List).cast<Map>().map((json) => School.fromJson(json)).toList();
@@ -43,32 +46,39 @@ class FilcAPI {
} catch (error) { } catch (error) {
print("ERROR: FilcAPI.getSchools: $error"); print("ERROR: FilcAPI.getSchools: $error");
} }
return null;
} }
static Future<Config?> getConfig(SettingsProvider settings) async { static Future<Config?> getConfig(SettingsProvider settings) async {
final userAgent = SettingsProvider.defaultSettings().config.userAgent;
Map<String, String> headers = { Map<String, String> headers = {
"x-filc-id": settings.xFilcId, "x-filc-id": settings.xFilcId,
"user-agent": SettingsProvider.defaultSettings().config.userAgent, "user-agent": userAgent,
}; };
log("[CONFIG] x-filc-id: \"${settings.xFilcId}\"");
log("[CONFIG] user-agent: \"$userAgent\"");
try { try {
http.Response res = await http.get(Uri.parse(CONFIG), headers: headers); http.Response res = await http.get(Uri.parse(config), headers: headers);
if (res.statusCode == 200) { if (res.statusCode == 200) {
return Config.fromJson(jsonDecode(res.body)); return Config.fromJson(jsonDecode(res.body));
} else if (res.statusCode == 429) { } else if (res.statusCode == 429) {
res = await http.get(Uri.parse(CONFIG)); res = await http.get(Uri.parse(config));
if (res.statusCode == 200) return Config.fromJson(jsonDecode(res.body)); if (res.statusCode == 200) return Config.fromJson(jsonDecode(res.body));
} }
throw "HTTP ${res.statusCode}: ${res.body}"; throw "HTTP ${res.statusCode}: ${res.body}";
} catch (error) { } catch (error) {
print("ERROR: FilcAPI.getConfig: $error"); print("ERROR: FilcAPI.getConfig: $error");
} }
return null;
} }
static Future<List<News>?> getNews() async { static Future<List<News>?> getNews() async {
try { try {
http.Response res = await http.get(Uri.parse(NEWS)); http.Response res = await http.get(Uri.parse(news));
if (res.statusCode == 200) { if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => News.fromJson(e)).toList(); return (jsonDecode(res.body) as List).cast<Map>().map((e) => News.fromJson(e)).toList();
@@ -78,11 +88,12 @@ class FilcAPI {
} catch (error) { } catch (error) {
print("ERROR: FilcAPI.getNews: $error"); print("ERROR: FilcAPI.getNews: $error");
} }
return null;
} }
static Future<Supporters?> getSupporters() async { static Future<Supporters?> getSupporters() async {
try { try {
http.Response res = await http.get(Uri.parse(SUPPORTERS)); http.Response res = await http.get(Uri.parse(supporters));
if (res.statusCode == 200) { if (res.statusCode == 200) {
return Supporters.fromJson(jsonDecode(res.body)); return Supporters.fromJson(jsonDecode(res.body));
@@ -92,11 +103,12 @@ class FilcAPI {
} catch (error) { } catch (error) {
print("ERROR: FilcAPI.getSupporters: $error"); print("ERROR: FilcAPI.getSupporters: $error");
} }
return null;
} }
static Future<List<Release>?> getReleases() async { static Future<List<Release>?> getReleases() async {
try { try {
http.Response res = await http.get(Uri.parse(RELEASES)); http.Response res = await http.get(Uri.parse(releases));
if (res.statusCode == 200) { if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => Release.fromJson(e)).toList(); return (jsonDecode(res.body) as List).cast<Map>().map((e) => Release.fromJson(e)).toList();
@@ -106,10 +118,11 @@ class FilcAPI {
} catch (error) { } catch (error) {
print("ERROR: FilcAPI.getReleases: $error"); print("ERROR: FilcAPI.getReleases: $error");
} }
return null;
} }
static Future<http.StreamedResponse?> downloadRelease(Release release) { static Future<http.StreamedResponse?> downloadRelease(Release release) {
if (release.downloads.length > 0) { if (release.downloads.isNotEmpty) {
try { try {
var client = http.Client(); var client = http.Client();
var request = http.Request('GET', Uri.parse(release.downloads.first)); var request = http.Request('GET', Uri.parse(release.downloads.first));
@@ -124,7 +137,7 @@ class FilcAPI {
static Future<void> sendReport(ErrorReport report) async { static Future<void> sendReport(ErrorReport report) async {
try { try {
http.Response res = await http.post(Uri.parse(REPORT), body: { http.Response res = await http.post(Uri.parse(reportApi), body: {
"os": report.os, "os": report.os,
"version": report.version, "version": report.version,
"error": report.error, "error": report.error,

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'package:filcnaplo/utils/jwt.dart'; import 'package:filcnaplo/utils/jwt.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';

View File

@@ -72,10 +72,11 @@ class NewsProvider extends ChangeNotifier {
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state); Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
if (_fresh > 0) if (_fresh > 0) {
show = true; show = true;
else } else {
show = false; show = false;
}
notifyListeners(); notifyListeners();
} }

View File

@@ -5,14 +5,14 @@ import 'package:http/http.dart' as http;
enum Status { network, maintenance, syncing } enum Status { network, maintenance, syncing }
class StatusProvider extends ChangeNotifier { class StatusProvider extends ChangeNotifier {
List<Status> _stack = []; final List<Status> _stack = [];
double _progress = 0.0; double _progress = 0.0;
StatusProvider() { StatusProvider() {
_handleNetworkChanges(); _handleNetworkChanges();
} }
Status? getStatus() => _stack.length > 0 ? _stack[0] : null; Status? getStatus() => _stack.isNotEmpty ? _stack[0] : null;
// Status progress from 0.0 to 1.0 // Status progress from 0.0 to 1.0
double get progress => _progress; double get progress => _progress;
@@ -64,10 +64,12 @@ class StatusProvider extends ChangeNotifier {
if (_progress == 1.0) { if (_progress == 1.0) {
notifyListeners(); notifyListeners();
// Wait for animation // Wait for animation
Future.delayed(Duration(milliseconds: 250), () { Future.delayed(const Duration(milliseconds: 250), () {
_stack.remove(Status.syncing); _stack.remove(Status.syncing);
notifyListeners(); notifyListeners();
}); });
} else if (progress != prev) notifyListeners(); } else if (progress != prev) {
notifyListeners();
}
} }
} }

View File

@@ -24,6 +24,7 @@ Future<void> syncAll(BuildContext context) {
// Lock // Lock
lock = true; lock = true;
// ignore: avoid_print
print("INFO Syncing all"); print("INFO Syncing all");
UserProvider user = Provider.of<UserProvider>(context, listen: false); UserProvider user = Provider.of<UserProvider>(context, listen: false);
@@ -42,7 +43,7 @@ Future<void> syncAll(BuildContext context) {
_syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()), _syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()),
_syncStatus(Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current())), _syncStatus(Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current())),
_syncStatus(Provider.of<ExamProvider>(context, listen: false).fetch()), _syncStatus(Provider.of<ExamProvider>(context, listen: false).fetch()),
_syncStatus(Provider.of<HomeworkProvider>(context, listen: false).fetch(from: DateTime.now().subtract(Duration(days: 30)))), _syncStatus(Provider.of<HomeworkProvider>(context, listen: false).fetch(from: DateTime.now().subtract(const Duration(days: 30)))),
_syncStatus(Provider.of<MessageProvider>(context, listen: false).fetchAll()), _syncStatus(Provider.of<MessageProvider>(context, listen: false).fetchAll()),
_syncStatus(Provider.of<NoteProvider>(context, listen: false).fetch()), _syncStatus(Provider.of<NoteProvider>(context, listen: false).fetch()),
_syncStatus(Provider.of<EventProvider>(context, listen: false).fetch()), _syncStatus(Provider.of<EventProvider>(context, listen: false).fetch()),

View File

@@ -3,14 +3,12 @@ import 'dart:io';
import 'package:filcnaplo/api/client.dart'; import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/release.dart'; import 'package:filcnaplo/models/release.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
class UpdateProvider extends ChangeNotifier { class UpdateProvider extends ChangeNotifier {
// Private // Private
late List<Release> _releases; late List<Release> _releases;
bool _available = false; bool _available = false;
bool get available => _available && _releases.length > 0; bool get available => _available && _releases.isNotEmpty;
PackageInfo? _packageInfo;
// Public // Public
List<Release> get releases => _releases; List<Release> get releases => _releases;
@@ -20,9 +18,10 @@ class UpdateProvider extends ChangeNotifier {
required BuildContext context, required BuildContext context,
}) { }) {
_releases = List.castFrom(initialReleases); _releases = List.castFrom(initialReleases);
PackageInfo.fromPlatform().then((value) => _packageInfo = value);
} }
static const currentVersion = String.fromEnvironment("APPVER", defaultValue: "1.0");
Future<void> fetch() async { Future<void> fetch() async {
if (!Platform.isAndroid) return; if (!Platform.isAndroid) return;
@@ -30,8 +29,9 @@ class UpdateProvider extends ChangeNotifier {
_releases.sort((a, b) => -a.version.compareTo(b.version)); _releases.sort((a, b) => -a.version.compareTo(b.version));
// Check for new releases // Check for new releases
if (_releases.length > 0) { if (_releases.isNotEmpty) {
_available = _packageInfo != null && _releases.first.version.compareTo(Version.fromString(_packageInfo?.version ?? "")) == 1; _available = _releases.first.version.compareTo(Version.fromString(currentVersion)) == 1;
// ignore: avoid_print
if (_available) print("INFO: New update: ${releases.first.version}"); if (_available) print("INFO: New update: ${releases.first.version}");
notifyListeners(); notifyListeners();
} }

View File

@@ -3,7 +3,7 @@ import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class UserProvider with ChangeNotifier { class UserProvider with ChangeNotifier {
Map<String, User> _users = {}; final Map<String, User> _users = {};
String? _selectedUserId; String? _selectedUserId;
User? get user => _users[_selectedUserId]; User? get user => _users[_selectedUserId];
@@ -23,7 +23,9 @@ class UserProvider with ChangeNotifier {
void addUser(User user) { void addUser(User user) {
_users[user.id] = user; _users[user.id] = user;
print("DEBUG: Added User: ${user.id} ${user.name}"); if (kDebugMode) {
print("DEBUG: Added User: ${user.id} ${user.name}");
}
} }
void removeUser(String userId) { void removeUser(String userId) {

View File

@@ -41,7 +41,7 @@ class App extends StatelessWidget {
final DatabaseProvider database; final DatabaseProvider database;
App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key) { App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key) {
if (user.getUsers().length > 0) user.setUser(user.getUsers().first.id); if (user.getUsers().isNotEmpty) user.setUser(user.getUsers().first.id);
} }
@override @override
@@ -99,18 +99,18 @@ class App extends StatelessWidget {
theme: AppTheme.lightTheme(context), theme: AppTheme.lightTheme(context),
darkTheme: AppTheme.darkTheme(context), darkTheme: AppTheme.darkTheme(context),
themeMode: themeMode.themeMode, themeMode: themeMode.themeMode,
localizationsDelegates: [ localizationsDelegates: const [
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
], ],
supportedLocales: [ supportedLocales: const [
const Locale('en', 'EN'), Locale('en', 'EN'),
const Locale('hu', 'HU'), Locale('hu', 'HU'),
const Locale('de', 'DE'), Locale('de', 'DE'),
], ],
localeListResolutionCallback: (locales, supported) { localeListResolutionCallback: (locales, supported) {
Locale locale = Locale('hu', 'HU'); Locale locale = const Locale('hu', 'HU');
for (var loc in locales ?? []) { for (var loc in locales ?? []) {
if (supported.contains(loc)) { if (supported.contains(loc)) {
@@ -122,7 +122,7 @@ class App extends StatelessWidget {
return locale; return locale;
}, },
onGenerateRoute: (settings) => rootNavigator(settings), onGenerateRoute: (settings) => rootNavigator(settings),
initialRoute: user.getUsers().length > 0 ? "navigation" : "login"); initialRoute: user.getUsers().isNotEmpty ? "navigation" : "login");
}, },
), ),
), ),
@@ -133,16 +133,17 @@ class App extends StatelessWidget {
// if platform == android || platform == ios // if platform == android || platform == ios
switch (route.name) { switch (route.name) {
case "login_back": case "login_back":
return CupertinoPageRoute(builder: (context) => LoginScreen(back: true)); return CupertinoPageRoute(builder: (context) => const LoginScreen(back: true));
case "login": case "login":
return _rootRoute(LoginScreen()); return _rootRoute(const LoginScreen());
case "navigation": case "navigation":
return _rootRoute(NavigationScreen()); return _rootRoute(const NavigationScreen());
case "login_to_navigation": case "login_to_navigation":
return loginRoute(NavigationScreen()); return loginRoute(const NavigationScreen());
case "settings": case "settings":
return settingsRoute(SettingsScreen()); return settingsRoute(const SettingsScreen());
} }
return null;
// else if platform == windows || ... // else if platform == windows || ...
} }

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'dart:io'; import 'dart:io';
import 'package:filcnaplo/database/struct.dart'; import 'package:filcnaplo/database/struct.dart';
@@ -69,7 +71,7 @@ Future<void> migrateDB(
) async { ) async {
var originalRows = await db.query(table); var originalRows = await db.query(table);
if (originalRows.length == 0) { if (originalRows.isEmpty) {
await db.execute("drop table $table"); await db.execute("drop table $table");
await create(db); await create(db);
return; return;
@@ -85,7 +87,7 @@ Future<void> migrateDB(
var copy = Map<String, dynamic>.from(original); var copy = Map<String, dynamic>.from(original);
// Fill missing columns // Fill missing columns
keys.forEach((key) { for (var key in keys) {
if (!keys.contains(key)) { if (!keys.contains(key)) {
print("DEBUG: dropping $key"); print("DEBUG: dropping $key");
copy.remove(key); copy.remove(key);
@@ -95,13 +97,13 @@ Future<void> migrateDB(
print("DEBUG: migrating $key"); print("DEBUG: migrating $key");
copy[key] = defaultValues[key]; copy[key] = defaultValues[key];
} }
}); }
migrated.add(copy); migrated.add(copy);
} }
}); });
if (migrated.length > 0) { if (migrated.isNotEmpty) {
// Delete table // Delete table
await db.execute("drop table $table"); await db.execute("drop table $table");

View File

@@ -28,9 +28,9 @@ class DatabaseQuery {
Future<UserProvider> getUsers() async { Future<UserProvider> getUsers() async {
var userProvider = UserProvider(); var userProvider = UserProvider();
List<Map> usersMap = await db.query("users"); List<Map> usersMap = await db.query("users");
usersMap.forEach((user) { for (var user in usersMap) {
userProvider.addUser(User.fromMap(user)); userProvider.addUser(User.fromMap(user));
}); }
return userProvider; return userProvider;
} }
} }
@@ -42,7 +42,7 @@ class UserDatabaseQuery {
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.length == 0) 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();
@@ -51,7 +51,7 @@ class UserDatabaseQuery {
Future<List<Lesson>> getLessons({required String userId}) async { Future<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.length == 0) 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 [];
List<Lesson> lessons = (jsonDecode(lessonsJson) as List).map((e) => Lesson.fromJson(e)).toList(); List<Lesson> lessons = (jsonDecode(lessonsJson) as List).map((e) => Lesson.fromJson(e)).toList();
@@ -60,7 +60,7 @@ class UserDatabaseQuery {
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.length == 0) 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();
@@ -69,7 +69,7 @@ class UserDatabaseQuery {
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.length == 0) 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();
@@ -78,7 +78,7 @@ class UserDatabaseQuery {
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.length == 0) 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();
@@ -87,7 +87,7 @@ class UserDatabaseQuery {
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.length == 0) 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();
@@ -96,7 +96,7 @@ class UserDatabaseQuery {
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.length == 0) 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();
@@ -105,7 +105,7 @@ class UserDatabaseQuery {
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.length == 0) return []; if (userData.isEmpty) return [];
String? absebcesJson = userData.elementAt(0)["absences"] as String?; String? absebcesJson = userData.elementAt(0)["absences"] as String?;
if (absebcesJson == null) return []; if (absebcesJson == null) return [];
List<Absence> absebces = (jsonDecode(absebcesJson) as List).map((e) => Absence.fromJson(e)).toList(); List<Absence> absebces = (jsonDecode(absebcesJson) as List).map((e) => Absence.fromJson(e)).toList();

View File

@@ -24,7 +24,7 @@ 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.length > 0) { 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());

View File

@@ -15,7 +15,7 @@ class DatabaseStruct {
break; break;
} }
return "${name} ${typeName.toUpperCase()} ${name == 'id' ? 'NOT NULL' : ''}"; return "$name ${typeName.toUpperCase()} ${name == 'id' ? 'NOT NULL' : ''}";
} }
@override @override

View File

@@ -1,9 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/helpers/storage_helper.dart'; import 'package:filcnaplo/helpers/storage_helper.dart';
import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/attachment.dart'; import 'package:filcnaplo_kreta_api/models/attachment.dart';
import 'package:filcnaplo_kreta_api/models/homework.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:open_file/open_file.dart'; import 'package:open_file/open_file.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -22,7 +24,29 @@ extension AttachmentHelper on Attachment {
Future<bool> open(BuildContext context) async { Future<bool> open(BuildContext context) async {
String downloads = await StorageHelper.downloadsPath(); String downloads = await StorageHelper.downloadsPath();
if (!await File("$downloads/$name").exists()) await download(context);
var result = await OpenFile.open("$downloads/$name");
return result.type == ResultType.done;
}
}
extension HomeworkAttachmentHelper on HomeworkAttachment {
Future<String> download(BuildContext context, {bool overwrite = false}) async {
String downloads = await StorageHelper.downloadsPath();
if (!overwrite && await File("$downloads/$name").exists()) return "$downloads/$name";
String url = downloadUrl(Provider.of<UserProvider>(context, listen: false).instituteCode ?? "");
Uint8List data = await Provider.of<KretaClient>(context, listen: false).getAPI(url, rawResponse: true);
if (!await StorageHelper.write("$downloads/$name", data)) return "";
return "$downloads/$name";
}
Future<bool> open(BuildContext context) async {
String downloads = await StorageHelper.downloadsPath();
if (!await File("$downloads/$name").exists()) await download(context); if (!await File("$downloads/$name").exists()) await download(context);
var result = await OpenFile.open("$downloads/$name"); var result = await OpenFile.open("$downloads/$name");
return result.type == ResultType.done; return result.type == ResultType.done;

View File

@@ -6,14 +6,15 @@ class AverageHelper {
List<String> ignoreInFinal = ["5,SzorgalomErtek", "4,MagatartasErtek"]; List<String> ignoreInFinal = ["5,SzorgalomErtek", "4,MagatartasErtek"];
if (finalAvg) if (finalAvg) {
grades.removeWhere((e) => grades.removeWhere((e) =>
(e.value.value == 0) || (e.value.value == 0) ||
(ignoreInFinal.contains(e.gradeType?.id))); (ignoreInFinal.contains(e.gradeType?.id)));
}
grades.forEach((e) { for (var e in grades) {
average += e.value.value * ((finalAvg ? 100 : e.value.weight) / 100); average += e.value.value * ((finalAvg ? 100 : e.value.weight) / 100);
}); }
average = average / average = average /
grades grades

View File

@@ -0,0 +1,36 @@
import 'package:flutter/cupertino.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:filcnaplo_mobile_ui/common/screens.i18n.dart';
const QuickActions quickActions = QuickActions();
void setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(type: 'action_grades', localizedTitle: 'grades'.i18n, icon: 'ic_grades'),
ShortcutItem(type: 'action_timetable', localizedTitle: 'timetable'.i18n, icon: 'ic_timetable'),
ShortcutItem(type: 'action_messages', localizedTitle: 'messages'.i18n, icon: 'ic_messages'),
ShortcutItem(type: 'action_absences', localizedTitle: 'absences'.i18n, icon: 'ic_absences')
]);
}
void handleQuickActions(BuildContext context, void Function(String) callback) {
quickActions.initialize((shortcutType) {
switch (shortcutType) {
case 'action_home':
callback("home");
break;
case 'action_grades':
callback("grades");
break;
case 'action_timetable':
callback("timetable");
break;
case 'action_messages':
callback("messages");
break;
case 'action_absences':
callback("absences");
break;
}
});
}

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';

View File

@@ -7,7 +7,7 @@ class SubjectIcon {
static IconData? lookup({Subject? subject, String? subjectName}) { static IconData? lookup({Subject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null)); assert(!(subject == null && subjectName == null));
String name = subject?.name.toLowerCase().specialChars().trim() ?? subjectName ?? ""; String name = (subject?.name ?? subjectName ?? "").toLowerCase().specialChars().trim();
String category = subject?.category.description.toLowerCase().specialChars() ?? ""; String category = subject?.category.description.toLowerCase().specialChars() ?? "";
// todo: check for categories // todo: check for categories
@@ -16,7 +16,7 @@ class SubjectIcon {
if (RegExp("irodalom").hasMatch(name)) return Icons.menu_book_outlined; if (RegExp("irodalom").hasMatch(name)) return Icons.menu_book_outlined;
if (RegExp("tor(i|tenelem)").hasMatch(name)) return Icons.hourglass_empty_outlined; if (RegExp("tor(i|tenelem)").hasMatch(name)) return Icons.hourglass_empty_outlined;
if (RegExp("foldrajz").hasMatch(name)) return Icons.public_outlined; if (RegExp("foldrajz").hasMatch(name)) return Icons.public_outlined;
if (RegExp("rajz|muvtori|muveszet").hasMatch(name)) return Icons.palette_outlined; if (RegExp("rajz|muvtori|muveszet|vizualis").hasMatch(name)) return Icons.palette_outlined;
if (RegExp("fizika").hasMatch(name)) return Icons.emoji_objects_outlined; if (RegExp("fizika").hasMatch(name)) return Icons.emoji_objects_outlined;
if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) return Icons.music_note_outlined; if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) return Icons.music_note_outlined;
if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) return Icons.sports_soccer_outlined; if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) return Icons.sports_soccer_outlined;

View File

@@ -7,33 +7,36 @@ import 'package:filcnaplo/helpers/storage_helper.dart';
import 'package:filcnaplo/models/release.dart'; import 'package:filcnaplo/models/release.dart';
import 'package:open_file/open_file.dart'; import 'package:open_file/open_file.dart';
enum UpdateState { prepare, downloading, installing } enum UpdateState { none, preparing, downloading, installing }
typedef UpdateCallback = Function(double progress, UpdateState state); typedef UpdateCallback = Function(double progress, UpdateState state);
// TODO: cleanup old apk files // TODO: cleanup old apk files
extension UpdateHelper on Release { extension UpdateHelper on Release {
Future<void> install({UpdateCallback? updateCallback}) async { Future<void> install({UpdateCallback? updateCallback}) async {
updateCallback!(-1, UpdateState.preparing);
String downloads = await StorageHelper.downloadsPath(); String downloads = await StorageHelper.downloadsPath();
File apk = File("$downloads/filcnaplo-${version}.apk"); File apk = File("$downloads/filcnaplo-$version.apk");
if (!await apk.exists()) { if (!await apk.exists()) {
updateCallback!(-1, UpdateState.downloading); updateCallback(-1, UpdateState.downloading);
var bytes = await download(updateCallback: updateCallback); var bytes = await download(updateCallback: updateCallback);
if (!await StorageHelper.write(apk.path, bytes)) throw "failed to write apk: permission denied"; if (!await StorageHelper.write(apk.path, bytes)) throw "failed to write apk: permission denied";
} }
updateCallback!(-1, UpdateState.installing); updateCallback(-1, UpdateState.installing);
var result = await OpenFile.open(apk.path); var result = await OpenFile.open(apk.path);
if (result.type != ResultType.done) { if (result.type != ResultType.done) {
// ignore: avoid_print
print("ERROR: installUpdate.openFile: " + result.message); print("ERROR: installUpdate.openFile: " + result.message);
throw result.message; throw result.message;
} }
updateCallback(-1, UpdateState.prepare); updateCallback(-1, UpdateState.none);
} }
Future<Uint8List> download({UpdateCallback? updateCallback}) async { Future<Uint8List> download({UpdateCallback? updateCallback}) async {

View File

@@ -1,8 +1,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class FilcIcons { class FilcIcons {
static const IconData home = const FilcIconData(0x41); static const IconData home = FilcIconData(0x41);
static const IconData linux = const FilcIconData(0x42); static const IconData linux = FilcIconData(0x42);
} }
class FilcIconData extends IconData { class FilcIconData extends IconData {

View File

@@ -53,10 +53,11 @@ Widget errorBuilder(FlutterErrorDetails details) {
errorShown = true; errorShown = true;
lastException = details.exceptionAsString(); lastException = details.exceptionAsString();
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(builder: (context) { Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(builder: (context) {
if (kReleaseMode) if (kReleaseMode) {
return ErrorReportScreen(details); return ErrorReportScreen(details);
else } else {
return ErrorScreen(details); return ErrorScreen(details);
}
})).then((_) => errorShown = false); })).then((_) => errorShown = false);
} }
}); });

View File

@@ -1,15 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
class Config { class Config {
String _userAgent; String _userAgent;
String? _version;
Map? json; Map? json;
static const String _version = String.fromEnvironment("APPVER", defaultValue: "2.2.0");
Config({required String userAgent, this.json}) : _userAgent = userAgent { Config({required String userAgent, this.json}) : _userAgent = userAgent;
PackageInfo.fromPlatform().then((value) => _version = value.version);
}
factory Config.fromJson(Map json) { factory Config.fromJson(Map json) {
return Config( return Config(
@@ -18,7 +14,7 @@ class Config {
); );
} }
String get userAgent => _userAgent.replaceAll("\$0", _version ?? "0").replaceAll("\$1", platform).replaceAll("\$2", "0"); String get userAgent => _userAgent.replaceAll("\$0", _version).replaceAll("\$1", platform).replaceAll("\$2", "0");
static String get platform { static String get platform {
if (Platform.isAndroid) { if (Platform.isAndroid) {

View File

@@ -58,10 +58,11 @@ class Version {
// check for valid prerelease name // check for valid prerelease name
if (p[0] != "") { if (p[0] != "") {
if (prereleases.contains(p[0].toLowerCase().trim())) if (prereleases.contains(p[0].toLowerCase().trim())) {
pre = p[0]; pre = p[0];
else } else {
throw "invalid prerelease name: ${p[0]}"; throw "invalid prerelease name: ${p[0]}";
}
} }
// core // core
@@ -74,6 +75,7 @@ class Version {
return Version(x, y, z, prerelease: pre, prever: prev); return Version(x, y, z, prerelease: pre, prever: prev);
} catch (error) { } catch (error) {
// ignore: avoid_print
print("WARNING: Failed to parse version ($o): $error"); print("WARNING: Failed to parse version ($o): $error");
return Version.zero; return Version.zero;
} }
@@ -126,4 +128,7 @@ class Version {
static const zero = Version(0, 0, 0); static const zero = Version(0, 0, 0);
static const List<String> prereleases = ["dev", "pre", "alpha", "beta", "rc"]; static const List<String> prereleases = ["dev", "pre", "alpha", "beta", "rc"];
@override
int get hashCode => toString().hashCode;
} }

View File

@@ -4,7 +4,6 @@ import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/config.dart'; import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/theme.dart'; import 'package:filcnaplo/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -13,8 +12,6 @@ enum UpdateChannel { stable, beta, dev }
enum VibrationStrength { off, light, medium, strong } enum VibrationStrength { off, light, medium, strong }
class SettingsProvider extends ChangeNotifier { class SettingsProvider extends ChangeNotifier {
PackageInfo? _packageInfo;
// en_en, hu_hu, de_de // en_en, hu_hu, de_de
String _language; String _language;
Pages _startPage; Pages _startPage;
@@ -44,7 +41,7 @@ class SettingsProvider extends ChangeNotifier {
int _notificationPollInterval; int _notificationPollInterval;
bool _developerMode; bool _developerMode;
VibrationStrength _vibrate; VibrationStrength _vibrate;
bool _ABweeks; bool _abWeeks;
bool _swapABweeks; bool _swapABweeks;
UpdateChannel _updateChannel; UpdateChannel _updateChannel;
Config _config; Config _config;
@@ -64,7 +61,7 @@ class SettingsProvider extends ChangeNotifier {
required bool developerMode, required bool developerMode,
required int notificationPollInterval, required int notificationPollInterval,
required VibrationStrength vibrate, required VibrationStrength vibrate,
required bool ABweeks, required bool abWeeks,
required bool swapABweeks, required bool swapABweeks,
required UpdateChannel updateChannel, required UpdateChannel updateChannel,
required Config config, required Config config,
@@ -82,15 +79,11 @@ class SettingsProvider extends ChangeNotifier {
_developerMode = developerMode, _developerMode = developerMode,
_notificationPollInterval = notificationPollInterval, _notificationPollInterval = notificationPollInterval,
_vibrate = vibrate, _vibrate = vibrate,
_ABweeks = ABweeks, _abWeeks = abWeeks,
_swapABweeks = swapABweeks, _swapABweeks = swapABweeks,
_updateChannel = updateChannel, _updateChannel = updateChannel,
_config = config, _config = config,
_xFilcId = xFilcId { _xFilcId = xFilcId;
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
_packageInfo = packageInfo;
});
}
factory SettingsProvider.fromMap(Map map) { factory SettingsProvider.fromMap(Map map) {
return SettingsProvider( return SettingsProvider(
@@ -113,7 +106,7 @@ class SettingsProvider extends ChangeNotifier {
notificationPollInterval: map["notification_poll_interval"], notificationPollInterval: map["notification_poll_interval"],
developerMode: map["developer_mode"] == 1 ? true : false, developerMode: map["developer_mode"] == 1 ? true : false,
vibrate: VibrationStrength.values[map["vibration_strength"]], vibrate: VibrationStrength.values[map["vibration_strength"]],
ABweeks: map["ab_weeks"] == 1 ? true : false, abWeeks: map["ab_weeks"] == 1 ? true : false,
swapABweeks: map["swap_ab_weeks"] == 1 ? true : false, swapABweeks: map["swap_ab_weeks"] == 1 ? true : false,
updateChannel: UpdateChannel.values[map["update_channel"]], updateChannel: UpdateChannel.values[map["update_channel"]],
config: Config.fromJson(jsonDecode(map["config"] ?? "{}")), config: Config.fromJson(jsonDecode(map["config"] ?? "{}")),
@@ -140,7 +133,7 @@ class SettingsProvider extends ChangeNotifier {
"grade_color5": _gradeColors[4].value, "grade_color5": _gradeColors[4].value,
"update_channel": _updateChannel.index, "update_channel": _updateChannel.index,
"vibration_strength": _vibrate.index, "vibration_strength": _vibrate.index,
"ab_weeks": _ABweeks ? 1 : 0, "ab_weeks": _abWeeks ? 1 : 0,
"swap_ab_weeks": _swapABweeks ? 1 : 0, "swap_ab_weeks": _swapABweeks ? 1 : 0,
"notification_poll_interval": _notificationPollInterval, "notification_poll_interval": _notificationPollInterval,
"config": jsonEncode(config.json), "config": jsonEncode(config.json),
@@ -169,11 +162,11 @@ class SettingsProvider extends ChangeNotifier {
developerMode: false, developerMode: false,
notificationPollInterval: 1, notificationPollInterval: 1,
vibrate: VibrationStrength.medium, vibrate: VibrationStrength.medium,
ABweeks: false, abWeeks: false,
swapABweeks: false, swapABweeks: false,
updateChannel: UpdateChannel.stable, updateChannel: UpdateChannel.stable,
config: Config.fromJson({}), config: Config.fromJson({}),
xFilcId: Uuid().v4(), xFilcId: const Uuid().v4(),
); );
} }
@@ -191,10 +184,9 @@ class SettingsProvider extends ChangeNotifier {
bool get developerMode => _developerMode; bool get developerMode => _developerMode;
int get notificationPollInterval => _notificationPollInterval; int get notificationPollInterval => _notificationPollInterval;
VibrationStrength get vibrate => _vibrate; VibrationStrength get vibrate => _vibrate;
bool get ABweeks => _ABweeks; bool get abWeeks => _abWeeks;
bool get swapABweeks => _swapABweeks; bool get swapABweeks => _swapABweeks;
UpdateChannel get updateChannel => _updateChannel; UpdateChannel get updateChannel => _updateChannel;
PackageInfo? get packageInfo => _packageInfo;
Config get config => _config; Config get config => _config;
String get xFilcId => _xFilcId; String get xFilcId => _xFilcId;
@@ -214,7 +206,7 @@ class SettingsProvider extends ChangeNotifier {
bool? developerMode, bool? developerMode,
int? notificationPollInterval, int? notificationPollInterval,
VibrationStrength? vibrate, VibrationStrength? vibrate,
bool? ABweeks, bool? abWeeks,
bool? swapABweeks, bool? swapABweeks,
UpdateChannel? updateChannel, UpdateChannel? updateChannel,
Config? config, Config? config,
@@ -231,16 +223,17 @@ class SettingsProvider extends ChangeNotifier {
if (notificationsEnabled != null && notificationsEnabled != _notificationsEnabled) _notificationsEnabled = notificationsEnabled; if (notificationsEnabled != null && notificationsEnabled != _notificationsEnabled) _notificationsEnabled = notificationsEnabled;
if (notificationsBitfield != null && notificationsBitfield != _notificationsBitfield) _notificationsBitfield = notificationsBitfield; if (notificationsBitfield != null && notificationsBitfield != _notificationsBitfield) _notificationsBitfield = notificationsBitfield;
if (developerMode != null && developerMode != _developerMode) _developerMode = developerMode; if (developerMode != null && developerMode != _developerMode) _developerMode = developerMode;
if (notificationPollInterval != null && notificationPollInterval != _notificationPollInterval) if (notificationPollInterval != null && notificationPollInterval != _notificationPollInterval) {
_notificationPollInterval = notificationPollInterval; _notificationPollInterval = notificationPollInterval;
}
if (vibrate != null && vibrate != _vibrate) _vibrate = vibrate; if (vibrate != null && vibrate != _vibrate) _vibrate = vibrate;
if (ABweeks != null && ABweeks != _ABweeks) _ABweeks = ABweeks; if (abWeeks != null && abWeeks != _abWeeks) _abWeeks = abWeeks;
if (swapABweeks != null && swapABweeks != _swapABweeks) _swapABweeks = swapABweeks; if (swapABweeks != null && swapABweeks != _swapABweeks) _swapABweeks = swapABweeks;
if (updateChannel != null && updateChannel != _updateChannel) _updateChannel = updateChannel; if (updateChannel != null && updateChannel != _updateChannel) _updateChannel = updateChannel;
if (config != null && config != _config) _config = config; if (config != null && config != _config) _config = config;
if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId; if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId;
if (database == null) database = Provider.of<DatabaseProvider>(context, listen: false); database ??= Provider.of<DatabaseProvider>(context, listen: false);
await database.store.storeSettings(this); await database.store.storeSettings(this);
notifyListeners(); notifyListeners();
} }

View File

@@ -26,7 +26,7 @@ class User {
if (id != null) { if (id != null) {
this.id = id; this.id = id;
} else { } else {
this.id = Uuid().v4(); this.id = const Uuid().v4();
} }
} }
@@ -67,7 +67,7 @@ class User {
"password": password, "password": password,
"institute_code": instituteCode, "institute_code": instituteCode,
"grant_type": "password", "grant_type": "password",
"client_id": KretaAPI.CLIENT_ID, "client_id": KretaAPI.clientId,
}; };
} }
} }

View File

@@ -11,14 +11,14 @@ class AppTheme {
// Light Theme // Light Theme
static ThemeData lightTheme(BuildContext context) { static ThemeData lightTheme(BuildContext context) {
var lightColors = LightAppColors(); var lightColors = LightAppColors();
Color accent = accentColorMap[Provider.of<SettingsProvider>(context, listen: false).accentColor] ?? Color(0); Color accent = accentColorMap[Provider.of<SettingsProvider>(context, listen: false).accentColor] ?? const Color(0x00000000);
return ThemeData( return ThemeData(
brightness: Brightness.light, brightness: Brightness.light,
fontFamily: _fontFamily, fontFamily: _fontFamily,
scaffoldBackgroundColor: lightColors.background, scaffoldBackgroundColor: lightColors.background,
backgroundColor: lightColors.highlight, backgroundColor: lightColors.highlight,
primaryColor: lightColors.filc, primaryColor: lightColors.filc,
dividerColor: Color(0), dividerColor: const Color(0x00000000),
colorScheme: ColorScheme.fromSwatch( colorScheme: ColorScheme.fromSwatch(
accentColor: accent, accentColor: accent,
backgroundColor: lightColors.background, backgroundColor: lightColors.background,
@@ -37,14 +37,14 @@ class AppTheme {
// Dark Theme // Dark Theme
static ThemeData darkTheme(BuildContext context) { static ThemeData darkTheme(BuildContext context) {
var darkColors = DarkAppColors(); var darkColors = DarkAppColors();
Color accent = accentColorMap[Provider.of<SettingsProvider>(context, listen: false).accentColor] ?? Color(0); Color accent = accentColorMap[Provider.of<SettingsProvider>(context, listen: false).accentColor] ?? const Color(0x00000000);
return ThemeData( return ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
fontFamily: _fontFamily, fontFamily: _fontFamily,
scaffoldBackgroundColor: darkColors.background, scaffoldBackgroundColor: darkColors.background,
backgroundColor: darkColors.highlight, backgroundColor: darkColors.highlight,
primaryColor: darkColors.filc, primaryColor: darkColors.filc,
dividerColor: Color(0), dividerColor: const Color(0x00000000),
colorScheme: ColorScheme.fromSwatch( colorScheme: ColorScheme.fromSwatch(
accentColor: accent, accentColor: accent,
backgroundColor: darkColors.background, backgroundColor: darkColors.background,
@@ -70,7 +70,7 @@ class AppColors {
enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple } enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple }
Map<AccentColor, Color> accentColorMap = { Map<AccentColor, Color> accentColorMap = {
AccentColor.filc: Color(0xff20AC9B), AccentColor.filc: const Color(0xff20AC9B),
AccentColor.blue: Colors.blue.shade300, AccentColor.blue: Colors.blue.shade300,
AccentColor.green: Colors.green.shade300, AccentColor.green: Colors.green.shade300,
AccentColor.lime: Colors.lime.shade300, AccentColor.lime: Colors.lime.shade300,
@@ -82,54 +82,82 @@ Map<AccentColor, Color> accentColorMap = {
}; };
abstract class ThemeAppColors { abstract class ThemeAppColors {
final Color shadow = Color(0); final Color shadow = const Color(0x00000000);
final Color text = Color(0); final Color text = const Color(0x00000000);
final Color background = Color(0); final Color background = const Color(0x00000000);
final Color highlight = Color(0); final Color highlight = const Color(0x00000000);
final Color red = Color(0); final Color red = const Color(0x00000000);
final Color orange = Color(0); final Color orange = const Color(0x00000000);
final Color yellow = Color(0); final Color yellow = const Color(0x00000000);
final Color green = Color(0); final Color green = const Color(0x00000000);
final Color filc = Color(0); final Color filc = const Color(0x00000000);
final Color teal = Color(0); final Color teal = const Color(0x00000000);
final Color blue = Color(0); final Color blue = const Color(0x00000000);
final Color indigo = Color(0); final Color indigo = const Color(0x00000000);
final Color purple = Color(0); final Color purple = const Color(0x00000000);
final Color pink = Color(0); final Color pink = const Color(0x00000000);
} }
class LightAppColors implements ThemeAppColors { class LightAppColors implements ThemeAppColors {
final shadow = Color(0xffE8E8E8); @override
final shadow = const Color(0xffE8E8E8);
@override
final text = Colors.black; final text = Colors.black;
final background = Color(0xffF4F9FF); @override
final highlight = Color(0xffFFFFFF); final background = const Color(0xffF4F9FF);
final red = Color(0xffFF3B30); @override
final orange = Color(0xffFF9500); final highlight = const Color(0xffFFFFFF);
final yellow = Color(0xffFFCC00); @override
final green = Color(0xff34C759); final red = const Color(0xffFF3B30);
final filc = Color(0xff247665); @override
final teal = Color(0xff5AC8FA); final orange = const Color(0xffFF9500);
final blue = Color(0xff007AFF); @override
final indigo = Color(0xff5856D6); final yellow = const Color(0xffFFCC00);
final purple = Color(0xffAF52DE); @override
final pink = Color(0xffFF2D55); final green = const Color(0xff34C759);
@override
final filc = const Color(0xff247665);
@override
final teal = const Color(0xff5AC8FA);
@override
final blue = const Color(0xff007AFF);
@override
final indigo = const Color(0xff5856D6);
@override
final purple = const Color(0xffAF52DE);
@override
final pink = const Color(0xffFF2D55);
} }
class DarkAppColors implements ThemeAppColors { class DarkAppColors implements ThemeAppColors {
final shadow = Color(0); @override
final shadow = const Color(0x00000000);
@override
final text = Colors.white; final text = Colors.white;
final background = Color(0xff000000); @override
final highlight = Color(0xff141516); final background = const Color(0xff000000);
final red = Color(0xffFF453A); @override
final orange = Color(0xffFF9F0A); final highlight = const Color(0xff141516);
final yellow = Color(0xffFFD60A); @override
final green = Color(0xff32D74B); final red = const Color(0xffFF453A);
final filc = Color(0xff29826F); @override
final teal = Color(0xff64D2FF); final orange = const Color(0xffFF9F0A);
final blue = Color(0xff0A84FF); @override
final indigo = Color(0xff5E5CE6); final yellow = const Color(0xffFFD60A);
final purple = Color(0xffBF5AF2); @override
final pink = Color(0xffFF375F); final green = const Color(0xff32D74B);
@override
final filc = const Color(0xff29826F);
@override
final teal = const Color(0xff64D2FF);
@override
final blue = const Color(0xff0A84FF);
@override
final indigo = const Color(0xff5E5CE6);
@override
final purple = const Color(0xffBF5AF2);
@override
final pink = const Color(0xffFF375F);
} }
class ThemeModeObserver extends ChangeNotifier { class ThemeModeObserver extends ChangeNotifier {

View File

@@ -6,8 +6,7 @@ import 'package:html/parser.dart';
import 'format.i18n.dart'; import 'format.i18n.dart';
extension StringFormatUtils on String { extension StringFormatUtils on String {
String specialChars() => this String specialChars() => replaceAll("é", "e")
.replaceAll("é", "e")
.replaceAll("á", "a") .replaceAll("á", "a")
.replaceAll("ó", "o") .replaceAll("ó", "o")
.replaceAll("ő", "o") .replaceAll("ő", "o")
@@ -17,9 +16,9 @@ extension StringFormatUtils on String {
.replaceAll("ü", "u") .replaceAll("ü", "u")
.replaceAll("í", "i"); .replaceAll("í", "i");
String capital() => this.length > 0 ? this[0].toUpperCase() + this.substring(1) : ""; String capital() => isNotEmpty ? this[0].toUpperCase() + substring(1) : "";
String capitalize() => this.split(" ").map((w) => w.capital()).join(" "); String capitalize() => split(" ").map((w) => w.capital()).join(" ");
String escapeHtml() { String escapeHtml() {
String htmlString = this; String htmlString = this;
@@ -33,28 +32,29 @@ extension StringFormatUtils on String {
} }
extension DateFormatUtils on DateTime { extension DateFormatUtils on DateTime {
String format(BuildContext context, {bool timeOnly = false, bool weekday = false}) { String format(BuildContext context, {bool timeOnly = false, bool forceToday = false, bool weekday = false}) {
// Time only // Time only
if (timeOnly) return DateFormat("HH:mm").format(this); if (timeOnly) return DateFormat("HH:mm").format(this);
DateTime now = DateTime.now(); DateTime now = DateTime.now();
if (now.year == this.year && now.month == this.month && now.day == this.day) { if (now.year == year && now.month == month && now.day == day) {
if (this.hour == 0 && this.minute == 0 && this.second == 0) return "Today".i18n; if (hour == 0 && minute == 0 && second == 0 || forceToday) return "Today".i18n;
return DateFormat("HH:mm").format(this); return DateFormat("HH:mm").format(this);
} }
if (now.year == this.year && now.month == this.month && now.subtract(Duration(days: 1)).day == this.day) return "Yesterday".i18n; if (now.year == year && now.month == month && now.subtract(const Duration(days: 1)).day == day) return "Yesterday".i18n;
if (now.year == this.year && now.month == this.month && now.add(Duration(days: 1)).day == this.day) return "Tomorrow".i18n; if (now.year == year && now.month == month && now.add(const Duration(days: 1)).day == day) return "Tomorrow".i18n;
String formatString; String formatString;
// If date is current week, show only weekday // If date is current week, show only weekday
if (Week.current().start.isBefore(this) && Week.current().end.isAfter(this)) if (Week.current().start.isBefore(this) && Week.current().end.isAfter(this)) {
formatString = "EEEE"; // ex. monday formatString = "EEEE";
else { } else {
if (this.year == now.year) if (year == now.year) {
formatString = "MMM dd."; // ex. Jan. 01. formatString = "MMM dd.";
else } else {
formatString = "yy/MM/dd"; // ex. 21/01/01 formatString = "yy/MM/dd";
} // ex. 21/01/01
if (weekday) formatString += " (EEEE)"; // ex. (monday) if (weekday) formatString += " (EEEE)"; // ex. (monday)
} }

View File

@@ -17,8 +17,10 @@ class JwtUtils {
var payload = utf8.decode(base64Url.decode(parts[1])); var payload = utf8.decode(base64Url.decode(parts[1]));
return jsonDecode(payload); return jsonDecode(payload);
} catch (error) { } catch (error) {
// ignore: avoid_print
print("ERROR: JwtUtils.decodeJwt: $error"); print("ERROR: JwtUtils.decodeJwt: $error");
} }
return null;
} }
static String? getNameFromJWT(String jwt) { static String? getNameFromJWT(String jwt) {
@@ -35,5 +37,6 @@ class JwtUtils {
case "Gondviselo": case "Gondviselo":
return Role.parent; return Role.parent;
} }
return null;
} }
} }

View File

@@ -3,10 +3,10 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez"
homepage: https://filcnaplo.hu homepage: https://filcnaplo.hu
publish_to: "none" publish_to: "none"
version: 3.1.1+139 version: 3.2.3+148
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.16.0-80.1.beta <3.0.0"
dependencies: dependencies:
flutter: flutter:
@@ -32,15 +32,17 @@ dependencies:
html: ^0.15.0 html: ^0.15.0
open_file: ^3.2.1 open_file: ^3.2.1
path_provider: ^2.0.2 path_provider: ^2.0.2
permission_handler: ^8.1.4+2 permission_handler: ^8.3.0
share_plus: ^3.0.4 share_plus: ^3.0.4
package_info_plus: ^1.0.6
connectivity_plus: ^2.0.2 connectivity_plus: ^2.0.2
flutter_displaymode: ^0.3.2 flutter_displaymode: ^0.3.2
quick_actions: ^0.6.0
implicitly_animated_reorderable_list: ^0.4.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^1.0.0
# flutter_launcher_icons: ^0.9.0 # flutter_launcher_icons: ^0.9.0
# flutter_native_splash: ^1.2.0 # flutter_native_splash: ^1.2.0
sqflite_common_ffi: ^2.0.0+3 sqflite_common_ffi: ^2.0.0+3

12
filcnaplo/run.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/fish
function get_version
cat pubspec.yaml | grep version: | cut -d' ' -f2 | cut -d+ -f1
end
if test -e /mnt/enc/keys/filc3.properties
set -x ANDROID_SIGNING /mnt/enc/keys/filc3.properties
end
flutter run --debug --dart-define=APPVER=(get_version)

6
fix-d8dx.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
cd $ANDROID_SDK/build-tools/31.0.0 &&
mv -v d8 dx &&
cd lib &&
mv -v d8.jar dx.jar