Compare commits

..

38 Commits
3.3.3 ... 3.4.1

Author SHA1 Message Date
55nknown
db58b54754 bump version 2022-11-21 13:27:39 +01:00
55nknown
7de3d211bb set user to last selected one 2022-11-21 12:41:22 +01:00
55nknown
d3e5fc99ea html fallback 2022-11-21 10:11:24 +01:00
55nknown
17cffc0576 revert goalplanner avghelper 2022-11-21 09:34:51 +01:00
55nknown
f38ff3b862 fix build 2022-11-20 23:03:50 +01:00
55nknown
89e67c369e fix build script 2022-11-20 22:26:27 +01:00
55nknown
6dd68c8f5d changelog 2022-11-20 22:18:21 +01:00
55nknown
97b01e9f14 fix build script 2022-11-20 22:03:46 +01:00
55nknown
556ba9d289 disable widget 2022-11-20 21:55:21 +01:00
55nknown
64868e85f3 widget setup 2022-11-20 21:02:32 +01:00
55nknown
30ac155b4b grade value nocolor 2022-11-20 20:18:58 +01:00
55nknown
36bd679644 cleanup 2022-11-20 19:27:06 +01:00
55nknown
75b03b95bc add premium backend 2022-11-20 19:25:04 +01:00
55nknown
ac18cf62c3 custom theme 2022-11-20 11:55:34 +01:00
55nknown
3619a7a4a7 nickname 2022-11-20 11:54:54 +01:00
55nknown
a71b365e4a deps 2022-11-20 11:38:31 +01:00
55nknown
20fa9a8aef migrate goalplanner 2022-11-17 13:44:12 +01:00
annon
da12ac8646 Goal planner backend (#132)
* refactor avghelper

* #59 backend

Co-authored-by: DarK-rtfm <44683230+DarK-rtfm@users.noreply.github.com>
2022-11-17 13:41:16 +01:00
55nknown
4b40692fe1 logic 2022-11-16 07:42:02 +01:00
55nknown
26f65a4144 fix livecard filter label lessons #128 2022-11-16 07:31:40 +01:00
55nknown
f8cfa04d04 nickname changing 2022-11-15 09:00:38 +01:00
55nknown
82671f5ec9 fix #125 2022-11-14 14:05:51 +01:00
55nknown
5d37de897f icon packs 2022-11-14 10:08:05 +01:00
55nknown
fcb1d8d6d9 fix #122 2022-11-14 09:14:08 +01:00
55nknown
446fad4c5f fix #124 2022-11-14 08:09:34 +01:00
55nknown
0f799375a1 fix #118 2022-11-14 08:02:50 +01:00
55nknown
29aa356cd0 Merge branch 'master' of https://github.com/filc/naplo 2022-11-14 07:39:07 +01:00
55nknown
3abaf2f96a ios livecard id 2022-11-14 07:38:49 +01:00
annon
5b48847cf2 Update FUNDING.yml 2022-11-07 18:19:11 +01:00
55nknown
b82a56ca65 new version 2022-11-02 08:08:49 +01:00
55nknown
94c57abdea mobile 2022-11-02 08:07:37 +01:00
55nknown
e3c26987ec kreta 2022-11-02 08:07:33 +01:00
55nknown
eac710a5b4 fix filters 2022-11-02 08:06:15 +01:00
55nknown
b8299c4daf mobile 2022-10-31 15:57:53 +01:00
55nknown
7a671b6aa6 desktop 2022-10-31 15:57:49 +01:00
55nknown
f51de83c88 fix updates 2022-10-31 15:57:25 +01:00
55nknown
d77cc081a9 changelog 2022-10-31 15:57:20 +01:00
55nknown
18fa9a6de7 build fix 2022-10-31 15:57:15 +01:00
48 changed files with 434 additions and 233 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
patreon: filcnaplo
github: filc

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# See https://www.dartlang.org/guides/libraries/private-files
termek.txt
.DS_Store
# Files and directories created by pub
.dart_tool/

View File

@@ -1,8 +1,6 @@
What's new:
- ✨ Meglepetés jegyek ✨
- 📸 Új ikonok
- Aprócska szépítgetések 🥂
- Jelenlegi óra jelző, gazdagoknak 👑
- Óriás visszaszámáló 👀
- Több szín lehetőség, egyedi hexadecimális szín választás
- Több tanuló esetén legutóbb kiválasztott tanuló mutatása indításkor
- Hibajavítások 🐛
- **Megérkezett a Filc Premium!** ✨

View File

@@ -26,13 +26,8 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file("$System.env.ANDROID_SIGNING")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
keystoreProperties.load(new FileInputStream(rootProject.file("signing/signing.properties")))
}
def keystorePropertiesFile = rootProject.file("$System.env.HOME/keys/filc3.properties")
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion rootProject.ext.compileSdkVersion

View File

@@ -6,8 +6,47 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</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://api.filcnaplo.hu -->
<data
android:scheme="https"
android:host="api.filcnaplo.hu"
android:pathPrefix="/callback" />
</intent-filter>
</activity>
<meta-data android:name="flutterEmbedding" android:value="2" />
<!-- <receiver android:name=".WidgetTimetable.widget_timetable"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_LEFT" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_RIGHT" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_TODAY" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_REFRESH" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/home_widget_test_info" />
</receiver>
<service android:name=".WidgetTimetable.widget_timetable_service"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
android:exported="true">
<intent-filter>
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
</intent-filter>
</receiver>
<service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
-->
</application>
<!-- Permissions -->

View File

@@ -2,8 +2,8 @@ buildscript {
ext.kotlin_version = '1.6.10'
ext {
compileSdkVersion = 31
targetSdkVersion = 31
compileSdkVersion = 33
targetSdkVersion = 33
appCompatVersion = "1.1.0"
}
@@ -33,7 +33,7 @@ subprojects {
afterEvaluate {project ->
if (project.plugins.hasPlugin('android') || project.plugins.hasPlugin('android-library')) {
android {
compileSdkVersion 31
compileSdkVersion 33
buildToolsVersion '31.0.0'
}
}

View File

@@ -1,4 +0,0 @@
keyAlias=test
keyPassword=test123
storeFile=../signing/signing.keystore
storePassword=test123

Binary file not shown.

12
filcnaplo/build.sh Normal file → Executable file
View File

@@ -1,4 +1,4 @@
#!/bin/fish
#!/usr/bin/env fish
# With build number
function get_version_bn
@@ -9,11 +9,5 @@ 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) --no-tree-shake-icons
cp -v "build/app/outputs/flutter-apk/app-release.apk" ~/"Desktop/hu.filc.naplo_"(get_version_bn).apk
notify-send "Flutter" "Apk build done."
flutter build apk --release --dart-define=APPVER=(get_version) --no-tree-shake-icons && \
cp -v "build/app/outputs/flutter-apk/app-release.apk" ~/"Desktop/hu.filc.naplo_"(get_version_bn).apk

View File

@@ -39,10 +39,6 @@ PODS:
- Flutter (1.0.0)
- flutter_custom_tabs (0.0.1):
- Flutter
- flutter_foreground_task (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
@@ -62,8 +58,6 @@ PODS:
- SDWebImage/Core (5.13.2)
- share_plus (0.0.1):
- Flutter
- shared_preferences_ios (0.0.1):
- Flutter
- sqflite (0.0.2):
- Flutter
- FMDB (>= 2.7.5)
@@ -77,15 +71,12 @@ DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_custom_tabs (from `.symlinks/plugins/flutter_custom_tabs/ios`)
- flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- live_activities (from `.symlinks/plugins/live_activities/ios`)
- open_file (from `.symlinks/plugins/open_file/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- 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`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -108,10 +99,6 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_custom_tabs:
:path: ".symlinks/plugins/flutter_custom_tabs/ios"
flutter_foreground_task:
:path: ".symlinks/plugins/flutter_foreground_task/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
live_activities:
:path: ".symlinks/plugins/live_activities/ios"
open_file:
@@ -124,8 +111,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/quick_actions_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios:
@@ -143,18 +128,15 @@ SPEC CHECKSUMS:
file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_custom_tabs: 7a10a08686955cb748e5d26e0ae586d30689bf89
flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
live_activities: 9ff56a06a2d43ecd68f56deeed13b18a8304789c
open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
quick_actions_ios: 5ec8f5f1ae81512ac803fe10c197ebb875767a9e
quick_actions_ios: 56c03753992beabaa6c598e470e12d430d6e5e2f
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de

View File

@@ -498,6 +498,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = MYUTW2GF6J;
@@ -516,8 +517,9 @@
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecard;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecardpro;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -537,6 +539,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = MYUTW2GF6J;
@@ -554,8 +557,9 @@
);
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecard;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecardpro;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
@@ -574,6 +578,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = MYUTW2GF6J;
@@ -591,8 +596,9 @@
);
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecard;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecardpro;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";

View File

@@ -10,7 +10,7 @@ import Flutter
GeneratedPluginRegistrant.register(with: self)
// here, Without this code the task will not work.
SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
//SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}

View File

@@ -1,2 +1 @@
#import "GeneratedPluginRegistrant.h"
#import <flutter_foreground_task/FlutterForegroundTaskPlugin.h>

View File

@@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:api.filcnaplo.hu</string>
</array>
</dict>
</plist>

View File

@@ -21,6 +21,8 @@ class FilcAPI {
// Private API
static const config = "https://api.filcnaplo.hu/config";
static const reportApi = "https://api.filcnaplo.hu/report";
static const premiumApi = "https://api.filcnaplo.hu/premium/activate";
static const premiumScopesApi = "https://api.filcnaplo.hu/premium/scopes";
// Updates
static const repo = "filc/naplo";
@@ -121,19 +123,16 @@ class FilcAPI {
return null;
}
static Future<http.StreamedResponse?> downloadRelease(Release release) {
if (release.downloads.isNotEmpty) {
static Future<http.StreamedResponse?> downloadRelease(ReleaseDownload release) {
try {
var client = http.Client();
var request = http.Request('GET', Uri.parse(release.downloads.first));
var request = http.Request('GET', Uri.parse(release.url));
return client.send(request);
} catch (error) {
print("ERROR: FilcAPI.downloadRelease: $error");
}
}
return Future.value(null);
}
}
static Future<void> sendReport(ErrorReport report) async {
try {

View File

@@ -22,8 +22,8 @@ class LiveCardProvider extends ChangeNotifier {
LiveCardState currentState = LiveCardState.empty;
late Timer _timer;
late final TimetableProvider _lessonProvider;
late final SettingsProvider _settingsProvider;
late final TimetableProvider _timetable;
late final SettingsProvider _settings;
late Duration _delay;
@@ -32,13 +32,13 @@ class LiveCardProvider extends ChangeNotifier {
Map<String, String> _lastActivity = {};
LiveCardProvider({
required TimetableProvider lessonProvider,
required SettingsProvider settingsProvider,
}) : _lessonProvider = lessonProvider,
_settingsProvider = settingsProvider {
required TimetableProvider timetable,
required SettingsProvider settings,
}) : _timetable = timetable,
_settings = settings {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => update());
lessonProvider.restore().then((_) => update());
_delay = settingsProvider.bellDelayEnabled ? Duration(seconds: settingsProvider.bellDelay) : Duration.zero;
timetable.restore().then((_) => update());
_delay = settings.bellDelayEnabled ? Duration(seconds: settings.bellDelay) : Duration.zero;
}
@override
@@ -73,7 +73,7 @@ class LiveCardProvider extends ChangeNotifier {
switch (currentState) {
case LiveCardState.duringLesson:
return {
"icon": currentLesson != null ? SubjectIcon.resolve(subject: currentLesson?.subject).name : "book",
"icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book",
"index": currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "",
"title": currentLesson != null ? ShortSubject.resolve(subject: currentLesson?.subject).capital() : "",
"subtitle": currentLesson?.room.replaceAll("_", " ") ?? "",
@@ -127,19 +127,20 @@ class LiveCardProvider extends ChangeNotifier {
}
}
List<Lesson> today = _today(_lessonProvider);
List<Lesson> today = _today(_timetable);
if (today.isEmpty) {
await _lessonProvider.fetch(week: Week.current());
today = _today(_lessonProvider);
await _timetable.fetch(week: Week.current());
today = _today(_timetable);
}
_delay = _settingsProvider.bellDelayEnabled ? Duration(seconds: _settingsProvider.bellDelay) : Duration.zero;
_delay = _settings.bellDelayEnabled ? Duration(seconds: _settings.bellDelay) : Duration.zero;
final now = _now().add(_delay);
// Filter cancelled lessons #20
today = today.where((lesson) => lesson.status?.name != "Elmaradt").toList();
// Filter label lessons #128
today = today.where((lesson) => lesson.status?.name != "Elmaradt" && lesson.subject.id != '' && !lesson.isEmpty).toList();
if (today.isNotEmpty) {
// sort

View File

@@ -41,7 +41,7 @@ class NewsProvider extends ChangeNotifier {
}
_state = state_;
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
Future<void> fetch() async {
@@ -53,7 +53,7 @@ class NewsProvider extends ChangeNotifier {
if (_fresh < 0) {
_state = news_.length;
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
_fresh = max(_fresh, 0);
@@ -72,7 +72,7 @@ class NewsProvider extends ChangeNotifier {
_fresh--;
_state++;
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
if (_fresh > 0) {
show = true;

View File

@@ -7,9 +7,12 @@ enum Status { network, maintenance, syncing }
class StatusProvider extends ChangeNotifier {
final List<Status> _stack = [];
double _progress = 0.0;
ConnectivityResult _networkType = ConnectivityResult.none;
ConnectivityResult get networkType => _networkType;
StatusProvider() {
_handleNetworkChanges();
Connectivity().checkConnectivity().then((value) => _networkType = value);
}
Status? getStatus() => _stack.isNotEmpty ? _stack[0] : null;
@@ -18,6 +21,7 @@ class StatusProvider extends ChangeNotifier {
void _handleNetworkChanges() {
Connectivity().onConnectivityChanged.listen((event) {
_networkType = event;
if (event == ConnectivityResult.none) {
if (!_stack.contains(Status.network)) {
_stack.insert(0, Status.network);

View File

@@ -1,3 +1,4 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:flutter/foundation.dart';
@@ -15,9 +16,16 @@ class UserProvider with ChangeNotifier {
String? get password => user?.password;
Role? get role => user?.role;
Student? get student => user?.student;
String? get nickname => user?.nickname;
String? get displayName => user?.displayName;
final SettingsProvider _settings;
UserProvider({required SettingsProvider settings}) : _settings = settings;
void setUser(String userId) {
_selectedUserId = userId;
_settings.update(lastAccountId: userId);
notifyListeners();
}

View File

@@ -45,15 +45,14 @@ import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/update_provider.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:filcnaplo_premium/providers/premium_provider.dart';
class App extends StatelessWidget {
final SettingsProvider settings;
final UserProvider user;
final DatabaseProvider database;
App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key) {
if (user.getUsers().isNotEmpty) user.setUser(user.getUsers().first.id);
}
const App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -62,20 +61,23 @@ class App extends StatelessWidget {
// Set high refresh mode #28
if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate();
WidgetsBinding.instance.addPostFrameCallback((_) {
FilcAPI.getConfig(settings).then((Config? config) {
if (config != null) settings.update(context, database: database, config: config);
});
});
CorePalette? corePalette;
final status = StatusProvider();
final kreta = KretaClient(user: user, settings: settings, status: status);
final timetable = TimetableProvider(user: user, database: database, kreta: kreta);
final premium = PremiumProvider(settings: settings);
WidgetsBinding.instance.addPostFrameCallback((_) {
FilcAPI.getConfig(settings).then((Config? config) {
if (config != null) settings.update(config: config);
});
premium.activate();
});
return MultiProvider(
providers: [
ChangeNotifierProvider<PremiumProvider>(create: (_) => premium),
ChangeNotifierProvider<SettingsProvider>(create: (_) => settings),
ChangeNotifierProvider<UserProvider>(create: (_) => user),
ChangeNotifierProvider<StatusProvider>(create: (_) => status),
@@ -86,7 +88,7 @@ class App extends StatelessWidget {
ChangeNotifierProvider<UpdateProvider>(create: (context) => UpdateProvider(context: context)),
// User data providers
ChangeNotifierProvider<GradeProvider>(create: (context) => GradeProvider(context: context)),
ChangeNotifierProvider<GradeProvider>(create: (_) => GradeProvider(settings: settings, user: user, database: database, kreta: kreta)),
ChangeNotifierProvider<TimetableProvider>(create: (_) => timetable),
ChangeNotifierProvider<ExamProvider>(create: (context) => ExamProvider(context: context)),
ChangeNotifierProvider<HomeworkProvider>(create: (context) => HomeworkProvider(context: context)),
@@ -95,8 +97,9 @@ class App extends StatelessWidget {
ChangeNotifierProvider<EventProvider>(create: (context) => EventProvider(context: context)),
ChangeNotifierProvider<AbsenceProvider>(create: (context) => AbsenceProvider(context: context)),
ChangeNotifierProvider<GradeCalculatorProvider>(create: (context) => GradeCalculatorProvider(context)),
ChangeNotifierProvider<LiveCardProvider>(create: (context) => LiveCardProvider(lessonProvider: timetable, settingsProvider: settings))
ChangeNotifierProvider<GradeCalculatorProvider>(
create: (_) => GradeCalculatorProvider(settings: settings, user: user, database: database, kreta: kreta)),
ChangeNotifierProvider<LiveCardProvider>(create: (context) => LiveCardProvider(timetable: timetable, settings: settings))
],
child: Consumer<ThemeModeObserver>(
builder: (context, themeMode, child) {

View File

@@ -2,6 +2,7 @@
import 'dart:io';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/database/struct.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:sqflite/sqflite.dart';
@@ -10,15 +11,17 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
const settingsDB = DatabaseStruct("settings", {
"language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int,
"update_channel": int, "config": String, // general
"update_channel": int, "config": String, "custom_accent_color": int, "custom_background_color": int, "custom_highlight_color": int, // general
"grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors
"vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int,
"notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "bell_delay": int, "bell_delay_enabled": int,
"grade_opening_fun": int,
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "premium_token": String, "last_account_id": String,
});
const usersDB = DatabaseStruct("users", {
"id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int,
"nickname": String // premium only
});
const usersDB = DatabaseStruct(
"users", {"id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int});
const userDataDB = DatabaseStruct("user_data", {
"id": String, "grades": String, "timetable": String, "exams": String, "homework": String, "messages": String, "notes": String,
"events": String, "absences": String, "group_averages": String,
@@ -28,7 +31,7 @@ const userDataDB = DatabaseStruct("user_data", {
Future<void> createTable(Database db, DatabaseStruct struct) => db.execute("CREATE TABLE IF NOT EXISTS ${struct.table} ($struct)");
Future<Database> initDB() async {
Future<Database> initDB(DatabaseProvider database) async {
Database db;
if (Platform.isLinux || Platform.isWindows) {
@@ -44,7 +47,7 @@ Future<Database> initDB() async {
if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first == 0) {
// Set default values for table Settings
await db.insert("settings", SettingsProvider.defaultSettings().toMap());
await db.insert("settings", SettingsProvider.defaultSettings(database: database).toMap());
}
// Migrate Databases
@@ -52,7 +55,7 @@ Future<Database> initDB() async {
await migrateDB(
db,
struct: settingsDB,
defaultValues: SettingsProvider.defaultSettings().toMap(),
defaultValues: SettingsProvider.defaultSettings(database: database).toMap(),
);
await migrateDB(
db,

View File

@@ -1,4 +1,5 @@
import 'dart:convert';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/subject_lesson_count.dart';
import 'package:filcnaplo/models/user.dart';
// ignore: depend_on_referenced_packages
@@ -22,18 +23,24 @@ class DatabaseQuery {
final Database db;
Future<SettingsProvider> getSettings() async {
Future<SettingsProvider> getSettings(DatabaseProvider database) async {
Map settingsMap = (await db.query("settings")).elementAt(0);
SettingsProvider settings = SettingsProvider.fromMap(settingsMap);
SettingsProvider settings = SettingsProvider.fromMap(settingsMap, database: database);
return settings;
}
Future<UserProvider> getUsers() async {
var userProvider = UserProvider();
Future<UserProvider> getUsers(SettingsProvider settings) async {
var userProvider = UserProvider(settings: settings);
List<Map> usersMap = await db.query("users");
for (var user in usersMap) {
userProvider.addUser(User.fromMap(user));
}
if (userProvider.getUsers().map((e) => e.id).contains(settings.lastAccountId)) {
userProvider.setUser(settings.lastAccountId);
} else {
userProvider.setUser(userProvider.getUsers().first.id);
settings.update(lastAccountId: userProvider.id);
}
return userProvider;
}
}

View File

@@ -7,19 +7,14 @@ class AverageHelper {
List<String> ignoreInFinal = ["5,SzorgalomErtek", "4,MagatartasErtek"];
if (finalAvg) {
grades.removeWhere((e) =>
(e.value.value == 0) ||
(ignoreInFinal.contains(e.gradeType?.id)));
grades.removeWhere((e) => (e.value.value == 0) || (ignoreInFinal.contains(e.gradeType?.id)));
}
for (var e in grades) {
average += e.value.value * ((finalAvg ? 100 : e.value.weight) / 100);
}
average = average /
grades
.map((e) => (finalAvg ? 100 : e.value.weight) / 100)
.fold(0.0, (a, b) => a + b);
average = average / grades.map((e) => (finalAvg ? 100 : e.value.weight) / 100).fold(0.0, (a, b) => a + b);
return average.isNaN ? 0.0 : average;
}

View File

@@ -5,6 +5,7 @@ import 'package:share_plus/share_plus.dart';
class ShareHelper {
static Future<void> shareText(String text, {String? subject}) => Share.share(text, subject: subject);
// ignore: deprecated_member_use
static Future<void> shareFile(String path, {String? text, String? subject}) => Share.shareFiles([path], text: text, subject: subject);
static Future<void> shareAttachment(Attachment attachment, {required BuildContext context}) async {

View File

@@ -1,20 +1,40 @@
import 'package:filcnaplo/icons/filc_icons.dart';
import 'package:filcnaplo/models/icon_pack.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
typedef SubjectIconVariants = Map<IconPack, IconData>;
class SubjectIconData {
final IconData data;
final SubjectIconVariants data;
final String name; // for iOS live activities compatibilty
SubjectIconData({
this.data = CupertinoIcons.rectangle_grid_2x2,
this.data = const {
IconPack.material: Icons.widgets_outlined,
IconPack.cupertino: CupertinoIcons.rectangle_grid_2x2,
},
this.name = "square.grid.2x2",
});
}
SubjectIconVariants createIcon({required IconData material, required IconData cupertino}) {
return {
IconPack.material: material,
IconPack.cupertino: cupertino,
};
}
class SubjectIcon {
static SubjectIconData resolve({Subject? subject, String? subjectName}) {
static String resolveName({Subject? subject, String? subjectName}) => _resolve(subject: subject, subjectName: subjectName).name;
static IconData resolveVariant({Subject? subject, String? subjectName, required BuildContext context}) =>
_resolve(subject: subject, subjectName: subjectName).data[Provider.of<SettingsProvider>(context, listen: false).iconPack]!;
static SubjectIconData _resolve({Subject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null));
String name = (subject?.name ?? subjectName ?? "").toLowerCase().specialChars().trim();
@@ -22,65 +42,70 @@ class SubjectIcon {
// todo: check for categories
if (RegExp("mate(k|matika)").hasMatch(name) || category == "matematika") {
return SubjectIconData(data: CupertinoIcons.function, name: "function");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.function, material: Icons.calculate_outlined), name: "function");
} else if (RegExp("magyar nyelv|nyelvtan").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.textformat_alt, name: "textformat.alt");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.textformat_alt, material: Icons.spellcheck_outlined), name: "textformat.alt");
} else if (RegExp("irodalom").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.book, name: "book");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.book, material: Icons.menu_book_outlined), name: "book");
} else if (RegExp("tor(i|tenelem)").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.compass, name: "safari");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.compass, material: Icons.hourglass_empty_outlined), name: "safari");
} else if (RegExp("foldrajz").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.map, name: "map");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.map, material: Icons.public_outlined), name: "map");
} else if (RegExp("rajz|muvtori|muveszet|vizualis").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.paintbrush, name: "paintbrush");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.paintbrush, material: Icons.palette_outlined), name: "paintbrush");
} else if (RegExp("fizika").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.lightbulb, name: "lightbulb");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.lightbulb, material: Icons.emoji_objects_outlined), name: "lightbulb");
} else if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.music_note, name: "music.note");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.music_note, material: Icons.music_note_outlined), name: "music.note");
} else if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.sportscourt, name: "sportscourt");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.sportscourt, material: Icons.sports_soccer_outlined), name: "sportscourt");
} else if (RegExp("kemia").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.lab_flask, name: "testtube.2");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.lab_flask, material: Icons.science_outlined), name: "testtube.2");
} else if (RegExp("biologia").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.paw, name: "pawprint");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.paw, material: Icons.pets_outlined), name: "pawprint");
} else if (RegExp("kornyezet|termeszet ?(tudomany|ismeret)|hon( es nep)?ismeret").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.arrow_3_trianglepath, name: "arrow.3.trianglepath");
return SubjectIconData(
data: createIcon(cupertino: CupertinoIcons.arrow_3_trianglepath, material: Icons.eco_outlined), name: "arrow.3.trianglepath");
} else if (RegExp("(hit|erkolcs)tan|vallas|etika").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.heart, name: "heart");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.heart, material: Icons.favorite_border_outlined), name: "heart");
} else if (RegExp("penzugy").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.money_dollar, name: "dollarsign");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.money_dollar, material: Icons.savings_outlined), name: "dollarsign");
} else if (RegExp("informatika|szoftver|iroda|digitalis").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.device_laptop, name: "laptopcomputer");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.device_laptop, material: Icons.computer_outlined), name: "laptopcomputer");
} else if (RegExp("prog").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.chevron_left_slash_chevron_right, name: "chevron.left.forwardslash.chevron.right");
return SubjectIconData(
data: createIcon(cupertino: CupertinoIcons.chevron_left_slash_chevron_right, material: Icons.code_outlined),
name: "chevron.left.forwardslash.chevron.right");
} else if (RegExp("halozat").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.antenna_radiowaves_left_right, name: "antenna.radiowaves.left.and.right");
return SubjectIconData(
data: createIcon(cupertino: CupertinoIcons.antenna_radiowaves_left_right, material: Icons.wifi_tethering_outlined),
name: "antenna.radiowaves.left.and.right");
} else if (RegExp("szinhaz").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.hifispeaker, name: "hifispeaker");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.hifispeaker, material: Icons.theater_comedy_outlined), name: "hifispeaker");
} else if (RegExp("film|media").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.film, name: "film");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.film, material: Icons.theaters_outlined), name: "film");
} else if (RegExp("elektro(tech)?nika").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.bolt, name: "bolt");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.bolt, material: Icons.electrical_services_outlined), name: "bolt");
} else if (RegExp("gepesz|mernok|ipar").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.wrench, name: "wrench");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.wrench, material: Icons.precision_manufacturing_outlined), name: "wrench");
} else if (RegExp("technika").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.hammer, name: "hammer");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.hammer, material: Icons.build_outlined), name: "hammer");
} else if (RegExp("tanc").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.music_mic, name: "music.mic");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.music_mic, material: Icons.speaker_outlined), name: "music.mic");
} else if (RegExp("filozofia").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.bubble_left, name: "bubble.left");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.bubble_left, material: Icons.psychology_outlined), name: "bubble.left");
} else if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name) || name == "ofo") {
return SubjectIconData(data: CupertinoIcons.group, name: "person.3");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.group, material: Icons.groups_outlined), name: "person.3");
} else if (RegExp("gazdasag").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.chart_pie, name: "chart.pie");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.chart_pie, material: Icons.account_balance_outlined), name: "chart.pie");
} else if (RegExp("szorgalom").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.checkmark_seal, name: "checkmark.seal");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.checkmark_seal, material: Icons.verified_outlined), name: "checkmark.seal");
} else if (RegExp("magatartas").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.smiley, name: "face.smiling");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.smiley, material: Icons.emoji_people_outlined), name: "face.smiling");
} else if (RegExp("angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv").hasMatch(name)) {
return SubjectIconData(data: CupertinoIcons.globe, name: "globe");
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.globe, material: Icons.translate_outlined), name: "globe");
} else if (RegExp("linux").hasMatch(name)) {
return SubjectIconData(data: FilcIcons.linux);
return SubjectIconData(data: createIcon(material: FilcIcons.linux, cupertino: FilcIcons.linux));
}
return SubjectIconData();

View File

@@ -40,7 +40,7 @@ extension UpdateHelper on Release {
}
Future<Uint8List> download({UpdateCallback? updateCallback}) async {
var response = await FilcAPI.downloadRelease(this);
var response = await FilcAPI.downloadRelease(downloads.first);
List<List<int>> chunks = [];
int downloaded = 0;

View File

@@ -16,4 +16,7 @@ class FilcIcons {
/// downstairs
static const IconData downstairs = IconData(0x03, fontFamily: iconFontFamily);
/// premium
static const IconData premium = IconData(0x04, fontFamily: iconFontFamily);
}

View File

@@ -32,12 +32,12 @@ class Startup {
late DatabaseProvider database;
Future<void> start() async {
var db = await initDB();
await db.close();
database = DatabaseProvider();
var db = await initDB(database);
await db.close();
await database.init();
settings = await database.query.getSettings();
user = await database.query.getUsers();
settings = await database.query.getSettings(database);
user = await database.query.getUsers(settings);
}
}

View File

@@ -0,0 +1 @@
enum IconPack { material, cupertino }

View File

@@ -1,9 +1,26 @@
class ReleaseDownload {
String url;
int size;
ReleaseDownload({
required this.url,
required this.size,
});
factory ReleaseDownload.fromJson(Map json) {
return ReleaseDownload(
url: json["browser_download_url"] ?? "",
size: json["size"] ?? 0,
);
}
}
class Release {
String tag;
Version version;
String author;
String body;
List<String> downloads;
List<ReleaseDownload> downloads;
bool prerelease;
Release({
@@ -20,7 +37,7 @@ class Release {
tag: json["tag_name"] ?? Version.zero.toString(),
author: json["author"] != null ? json["author"]["login"] ?? "" : "",
body: json["body"] ?? "",
downloads: json["assets"] != null ? json["assets"].map((a) => a["browser_download_url"] ?? "").toList().cast<String>() : [],
downloads: json["assets"] != null ? json["assets"].map((a) => ReleaseDownload.fromJson(a)).toList().cast<ReleaseDownload>() : [],
prerelease: json["prerelease"] ?? false,
version: Version.fromString(json["tag_name"] ?? ""),
);

View File

@@ -3,10 +3,10 @@ import 'dart:developer';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/models/icon_pack.dart';
import 'package:filcnaplo/theme/colors/accent.dart';
import 'package:filcnaplo/theme/colors/dark_mobile.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
enum Pages { home, grades, timetable, messages, absences }
@@ -16,6 +16,8 @@ enum UpdateChannel { stable, beta, dev }
enum VibrationStrength { off, light, medium, strong }
class SettingsProvider extends ChangeNotifier {
final DatabaseProvider? _database;
// en_en, hu_hu, de_de
String _language;
Pages _startPage;
@@ -56,8 +58,16 @@ class SettingsProvider extends ChangeNotifier {
bool _bellDelayEnabled;
int _bellDelay;
bool _gradeOpeningFun;
IconPack _iconPack;
Color _customAccentColor;
Color _customBackgroundColor;
Color _customHighlightColor;
List<String> _premiumScopes;
String _premiumAccessToken;
String _lastAccountId;
SettingsProvider({
DatabaseProvider? database,
required String language,
required Pages startPage,
required int rounding,
@@ -82,7 +92,15 @@ class SettingsProvider extends ChangeNotifier {
required bool bellDelayEnabled,
required int bellDelay,
required bool gradeOpeningFun,
}) : _language = language,
required IconPack iconPack,
required Color customAccentColor,
required Color customBackgroundColor,
required Color customHighlightColor,
required List<String> premiumScopes,
required String premiumAccessToken,
required String lastAccountId,
}) : _database = database,
_language = language,
_startPage = startPage,
_rounding = rounding,
_theme = theme,
@@ -105,9 +123,16 @@ class SettingsProvider extends ChangeNotifier {
_presentationMode = presentationMode,
_bellDelayEnabled = bellDelayEnabled,
_bellDelay = bellDelay,
_gradeOpeningFun = gradeOpeningFun;
_gradeOpeningFun = gradeOpeningFun,
_iconPack = iconPack,
_customAccentColor = customAccentColor,
_customBackgroundColor = customBackgroundColor,
_customHighlightColor = customHighlightColor,
_premiumScopes = premiumScopes,
_premiumAccessToken = premiumAccessToken,
_lastAccountId = lastAccountId;
factory SettingsProvider.fromMap(Map map) {
factory SettingsProvider.fromMap(Map map, {required DatabaseProvider database}) {
Map<String, Object?>? configMap;
try {
@@ -117,6 +142,7 @@ class SettingsProvider extends ChangeNotifier {
}
return SettingsProvider(
database: database,
language: map["language"],
startPage: Pages.values[map["start_page"]],
rounding: map["rounding"],
@@ -147,6 +173,13 @@ class SettingsProvider extends ChangeNotifier {
bellDelayEnabled: map["bell_delay_enabled"] == 1,
bellDelay: map["bell_delay"],
gradeOpeningFun: map["grade_opening_fun"] == 1,
iconPack: Map.fromEntries(IconPack.values.map((e) => MapEntry(e.name, e)))[map["icon_pack"]]!,
customAccentColor: Color(map["custom_accent_color"]),
customBackgroundColor: Color(map["custom_background_color"]),
customHighlightColor: Color(map["custom_highlight_color"]),
premiumScopes: jsonDecode(map["premium_scopes"]).cast<String>(),
premiumAccessToken: map["premium_token"],
lastAccountId: map["last_account_id"],
);
}
@@ -179,11 +212,19 @@ class SettingsProvider extends ChangeNotifier {
"bell_delay_enabled": _bellDelayEnabled ? 1 : 0,
"bell_delay": _bellDelay,
"grade_opening_fun": _gradeOpeningFun ? 1 : 0,
"icon_pack": _iconPack.name,
"custom_accent_color": _customAccentColor.value,
"custom_background_color": _customBackgroundColor.value,
"custom_highlight_color": _customHighlightColor.value,
"premium_scopes": jsonEncode(_premiumScopes),
"premium_token": _premiumAccessToken,
"last_account_id": _lastAccountId,
};
}
factory SettingsProvider.defaultSettings() {
factory SettingsProvider.defaultSettings({DatabaseProvider? database}) {
return SettingsProvider(
database: database,
language: "hu",
startPage: Pages.home,
rounding: 5,
@@ -214,6 +255,13 @@ class SettingsProvider extends ChangeNotifier {
bellDelayEnabled: false,
bellDelay: 0,
gradeOpeningFun: true,
iconPack: IconPack.cupertino,
customAccentColor: const Color(0xff20AC9B),
customBackgroundColor: const Color(0xff000000),
customHighlightColor: const Color(0xff222222),
premiumScopes: [],
premiumAccessToken: "",
lastAccountId: "",
);
}
@@ -242,10 +290,15 @@ class SettingsProvider extends ChangeNotifier {
bool get bellDelayEnabled => _bellDelayEnabled;
int get bellDelay => _bellDelay;
bool get gradeOpeningFun => _gradeOpeningFun;
IconPack get iconPack => _iconPack;
Color? get customAccentColor => _customAccentColor == accentColorMap[AccentColor.custom] ? null : _customAccentColor;
Color? get customBackgroundColor => _customBackgroundColor;
Color? get customHighlightColor => _customHighlightColor;
List<String> get premiumScopes => _premiumScopes;
String get premiumAccessToken => _premiumAccessToken;
String get lastAccountId => _lastAccountId;
Future<void> update(
BuildContext context, {
DatabaseProvider? database,
Future<void> update({
bool store = true,
String? language,
Pages? startPage,
@@ -271,6 +324,13 @@ class SettingsProvider extends ChangeNotifier {
bool? bellDelayEnabled,
int? bellDelay,
bool? gradeOpeningFun,
IconPack? iconPack,
Color? customAccentColor,
Color? customBackgroundColor,
Color? customHighlightColor,
List<String>? premiumScopes,
String? premiumAccessToken,
String? lastAccountId,
}) async {
if (language != null && language != _language) _language = language;
if (startPage != null && startPage != _startPage) _startPage = startPage;
@@ -298,9 +358,15 @@ class SettingsProvider extends ChangeNotifier {
if (bellDelay != null && bellDelay != _bellDelay) _bellDelay = bellDelay;
if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled) _bellDelayEnabled = bellDelayEnabled;
if (gradeOpeningFun != null && gradeOpeningFun != _gradeOpeningFun) _gradeOpeningFun = gradeOpeningFun;
if (iconPack != null && iconPack != _iconPack) _iconPack = iconPack;
if (customAccentColor != null && customAccentColor != _customAccentColor) _customAccentColor = customAccentColor;
if (customBackgroundColor != null && customBackgroundColor != _customBackgroundColor) _customBackgroundColor = customBackgroundColor;
if (customHighlightColor != null && customHighlightColor != _customHighlightColor) _customHighlightColor = customHighlightColor;
if (premiumScopes != null && premiumScopes != _premiumScopes) _premiumScopes = premiumScopes;
if (premiumAccessToken != null && premiumAccessToken != _premiumAccessToken) _premiumAccessToken = premiumAccessToken;
if (lastAccountId != null && lastAccountId != _lastAccountId) _lastAccountId = lastAccountId;
database ??= Provider.of<DatabaseProvider>(context, listen: false);
if (store) await database.store.storeSettings(this);
if (store) await _database?.store.storeSettings(this);
notifyListeners();
}
}

View File

@@ -13,6 +13,9 @@ class User {
String name;
Student student;
Role role;
String nickname;
String get displayName => nickname != '' ? nickname : name;
User({
String? id,
@@ -22,6 +25,7 @@ class User {
required this.instituteCode,
required this.student,
required this.role,
this.nickname = "",
}) {
if (id != null) {
this.id = id;
@@ -39,6 +43,7 @@ class User {
name: map["name"].trim(),
student: Student.fromJson(jsonDecode(map["student"])),
role: Role.values[map["role"] ?? 0],
nickname: map["nickname"] ?? "",
);
}
@@ -51,6 +56,7 @@ class User {
"name": name,
"student": jsonEncode(student.json),
"role": role.index,
"nickname": nickname,
};
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple, adaptive }
enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple, adaptive, custom }
Map<AccentColor, Color> accentColorMap = {
AccentColor.filc: const Color(0xff20AC9B),
@@ -13,4 +13,5 @@ Map<AccentColor, Color> accentColorMap = {
AccentColor.pink: Colors.pink.shade300,
AccentColor.purple: Colors.purple.shade300,
AccentColor.adaptive: const Color(0xff20AC9B),
AccentColor.custom: const Color(0xff20AC9B),
};

View File

@@ -22,8 +22,10 @@ class AppTheme {
// Light Theme
static ThemeData lightTheme(BuildContext context, {CorePalette? palette}) {
var lightColors = AppColors.fromBrightness(Brightness.light);
AccentColor accentColor = Provider.of<SettingsProvider>(context, listen: false).accentColor;
Color accent = accentColorMap[accentColor] ?? const Color(0x00000000);
final settings = Provider.of<SettingsProvider>(context, listen: false);
AccentColor accentColor = settings.accentColor;
final customAccentColor = accentColor == AccentColor.custom ? settings.customAccentColor : null;
Color accent = customAccentColor ?? accentColorMap[accentColor] ?? const Color(0x00000000);
if (accentColor == AccentColor.adaptive) {
if (palette != null) accent = _paletteAccentLight(palette)!;
@@ -31,31 +33,35 @@ class AppTheme {
palette = null;
}
Color backgroundColor =
accentColor == AccentColor.custom ? settings.customBackgroundColor : _paletteBackgroundLight(palette) ?? lightColors.background;
Color highlighColor =
accentColor == AccentColor.custom ? settings.customHighlightColor : _paletteHighlightLight(palette) ?? lightColors.highlight;
return ThemeData(
brightness: Brightness.light,
useMaterial3: false,
fontFamily: _fontFamily,
scaffoldBackgroundColor: _paletteBackgroundLight(palette) ?? lightColors.background,
backgroundColor: _paletteHighlightLight(palette) ?? lightColors.highlight,
scaffoldBackgroundColor: backgroundColor,
backgroundColor: highlighColor,
primaryColor: lightColors.filc,
dividerColor: const Color(0x00000000),
colorScheme: ColorScheme.fromSwatch(
accentColor: accent,
backgroundColor: _paletteBackgroundLight(palette) ?? lightColors.background,
backgroundColor: backgroundColor,
brightness: Brightness.light,
cardColor: _paletteHighlightLight(palette) ?? lightColors.highlight,
cardColor: highlighColor,
errorColor: lightColors.red,
primaryColorDark: lightColors.filc,
primarySwatch: Colors.teal,
),
shadowColor: lightColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: _paletteBackgroundLight(palette) ?? lightColors.background),
shadowColor: highlighColor.withOpacity(.5), //lightColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: backgroundColor),
indicatorColor: accent,
iconTheme: IconThemeData(color: lightColors.text.withOpacity(.75)),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: accent.withOpacity(accentColor == AccentColor.adaptive ? 0.4 : 0.8),
iconTheme: MaterialStateProperty.all(IconThemeData(color: lightColors.text)),
backgroundColor: _paletteHighlightLight(palette) ?? lightColors.highlight,
backgroundColor: highlighColor,
labelTextStyle: MaterialStateProperty.all(TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w500,
@@ -75,8 +81,10 @@ class AppTheme {
// Dark Theme
static ThemeData darkTheme(BuildContext context, {CorePalette? palette}) {
var darkColors = AppColors.fromBrightness(Brightness.dark);
AccentColor accentColor = Provider.of<SettingsProvider>(context, listen: false).accentColor;
Color accent = accentColorMap[accentColor] ?? const Color(0x00000000);
final settings = Provider.of<SettingsProvider>(context, listen: false);
AccentColor accentColor = settings.accentColor;
final customAccentColor = accentColor == AccentColor.custom ? settings.customAccentColor : null;
Color accent = customAccentColor ?? accentColorMap[accentColor] ?? const Color(0x00000000);
if (accentColor == AccentColor.adaptive) {
if (palette != null) accent = _paletteAccentDark(palette)!;
@@ -84,31 +92,34 @@ class AppTheme {
palette = null;
}
Color backgroundColor =
accentColor == AccentColor.custom ? settings.customBackgroundColor : _paletteBackgroundDark(palette) ?? darkColors.background;
Color highlightColor = accentColor == AccentColor.custom ? settings.customHighlightColor : _paletteHighlightDark(palette) ?? darkColors.highlight;
return ThemeData(
brightness: Brightness.dark,
useMaterial3: false,
fontFamily: _fontFamily,
scaffoldBackgroundColor: _paletteBackgroundDark(palette) ?? darkColors.background,
backgroundColor: _paletteHighlightDark(palette) ?? darkColors.highlight,
scaffoldBackgroundColor: backgroundColor,
backgroundColor: highlightColor,
primaryColor: darkColors.filc,
dividerColor: const Color(0x00000000),
colorScheme: ColorScheme.fromSwatch(
accentColor: accent,
backgroundColor: _paletteBackgroundDark(palette) ?? darkColors.background,
backgroundColor: backgroundColor,
brightness: Brightness.dark,
cardColor: _paletteHighlightDark(palette) ?? darkColors.highlight,
cardColor: highlightColor,
errorColor: darkColors.red,
primaryColorDark: darkColors.filc,
primarySwatch: Colors.teal,
),
shadowColor: darkColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: _paletteBackgroundDark(palette) ?? darkColors.background),
shadowColor: highlightColor.withOpacity(.5), //darkColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: backgroundColor),
indicatorColor: accent,
iconTheme: IconThemeData(color: darkColors.text.withOpacity(.75)),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: accent.withOpacity(accentColor == AccentColor.adaptive ? 0.4 : 0.8),
iconTheme: MaterialStateProperty.all(IconThemeData(color: darkColors.text)),
backgroundColor: _paletteHighlightDark(palette) ?? darkColors.highlight,
backgroundColor: highlightColor,
labelTextStyle: MaterialStateProperty.all(TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w500,

View File

@@ -1,5 +1,5 @@
import 'package:filcnaplo/api/providers/update_provider.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/ui/date_widget.dart';
import 'package:filcnaplo/ui/filter/widgets/grades.dart' as grade_filter;
import 'package:filcnaplo/ui/filter/widgets/certifications.dart' as certification_filter;
@@ -12,6 +12,7 @@ import 'package:filcnaplo/ui/filter/widgets/events.dart' as event_filter;
import 'package:filcnaplo/ui/filter/widgets/lessons.dart' as lesson_filter;
import 'package:filcnaplo/ui/filter/widgets/update.dart' as update_filter;
import 'package:filcnaplo/ui/filter/widgets/missed_exams.dart' as missed_exam_filter;
import 'package:filcnaplo/ui/filter/widgets/premium.dart' as premium_filter;
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
@@ -20,6 +21,7 @@ import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:filcnaplo_premium/providers/premium_provider.dart';
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list/transitions.dart';
@@ -27,7 +29,7 @@ import 'package:provider/provider.dart';
const List<FilterType> homeFilters = [FilterType.all, FilterType.grades, FilterType.messages, FilterType.absences];
enum FilterType { all, grades, newGrades, messages, absences, homework, exams, notes, events, lessons, updates, certifications, missedExams }
enum FilterType { all, grades, newGrades, messages, absences, homework, exams, notes, events, lessons, updates, certifications, missedExams, premium }
Future<List<DateWidget>> getFilterWidgets(FilterType activeData, {bool absencesNoExcused = false, required BuildContext context}) async {
final gradeProvider = Provider.of<GradeProvider>(context);
@@ -39,6 +41,8 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData, {bool absencesN
final noteProvider = Provider.of<NoteProvider>(context);
final eventProvider = Provider.of<EventProvider>(context);
final updateProvider = Provider.of<UpdateProvider>(context);
final settingsProvider = Provider.of<SettingsProvider>(context);
final premiumProvider = Provider.of<PremiumProvider>(context);
List<DateWidget> items = [];
@@ -55,6 +59,7 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData, {bool absencesN
getFilterWidgets(FilterType.updates, context: context),
getFilterWidgets(FilterType.certifications, context: context),
getFilterWidgets(FilterType.missedExams, context: context),
getFilterWidgets(FilterType.premium, context: context),
]);
items = all.expand((x) => x).toList();
@@ -63,7 +68,9 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData, {bool absencesN
// Grades
case FilterType.grades:
items = grade_filter.getWidgets(gradeProvider.grades, gradeProvider.lastSeenDate);
if (settingsProvider.gradeOpeningFun) {
items.addAll(await getFilterWidgets(FilterType.newGrades, context: context));
}
break;
// Grades
@@ -117,13 +124,19 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData, {bool absencesN
// Updates
case FilterType.updates:
if (updateProvider.releases.isNotEmpty) items = [update_filter.getWidget(updateProvider.releases.first)];
if (updateProvider.available) items = [update_filter.getWidget(updateProvider.releases.first)];
break;
// Missed Exams
case FilterType.missedExams:
items = missed_exam_filter.getWidgets(timetableProvider.lessons);
break;
case FilterType.premium:
final now = DateTime.now();
final isWeekend = now.weekday == DateTime.saturday || now.weekday == DateTime.sunday;
items = [if (!premiumProvider.hasPremium && isWeekend) premium_filter.getWidget()];
break;
}
return items;
}
@@ -145,11 +158,11 @@ Widget filterItemBuilder(BuildContext context, Animation<double> animation, Widg
child: DecoratedBox(
decoration: BoxDecoration(
boxShadow: [
if (Theme.of(context).brightness == Brightness.light)
BoxShadow(
offset: const Offset(0, 21),
blurRadius: 23.0,
color: AppColors.of(context).shadow.withOpacity(
color: Theme.of(context).shadowColor.withOpacity(
Theme.of(context).shadowColor.opacity *
CurvedAnimation(
parent: CurvedAnimation(parent: animation, curve: Curves.easeInOutCubic),
curve: const Interval(2 / 3, 1.0),

View File

@@ -8,7 +8,8 @@ import 'package:filcnaplo_desktop_ui/common/widgets/grade/grade_viewable.dart' a
List<DateWidget> getWidgets(List<Grade> providerGrades, DateTime? lastSeenDate) {
List<DateWidget> items = [];
for (var grade in providerGrades) {
if (grade.type == GradeType.midYear && !(lastSeenDate != null && grade.date.isAfter(lastSeenDate))) {
final surprise = (!(lastSeenDate != null && grade.date.isAfter(lastSeenDate)) || grade.value.value == 0);
if (grade.type == GradeType.midYear && surprise) {
items.add(DateWidget(
key: grade.id,
date: grade.date,
@@ -23,7 +24,8 @@ List<DateWidget> getNewWidgets(List<Grade> providerGrades, DateTime? lastSeenDat
List<DateWidget> items = [];
List<Grade> newGrades = [];
for (var grade in providerGrades) {
if (grade.type == GradeType.midYear && !(lastSeenDate != null && !grade.date.isAfter(lastSeenDate))) {
final surprise = !(lastSeenDate != null && !grade.date.isAfter(lastSeenDate)) && grade.value.value != 0;
if (grade.type == GradeType.midYear && surprise) {
newGrades.add(grade);
}
}

View File

@@ -4,15 +4,12 @@ import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_viewable.da
List<DateWidget> getWidgets(List<Homework> providerHomework) {
List<DateWidget> items = [];
final now = DateTime.now();
providerHomework.where((h) => h.deadline.hour == 0 ? _sameDate(h.deadline, now) : h.deadline.isAfter(now)).forEach((homework) {
for (var homework in providerHomework) {
items.add(DateWidget(
key: homework.id,
date: homework.deadline.year != 0 ? homework.deadline : homework.date,
widget: mobile.HomeworkViewable(homework),
));
});
}
return items;
}
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);

View File

@@ -7,7 +7,7 @@ List<DateWidget> getWidgets(List<Lesson> providerLessons) {
providerLessons.where((l) => l.isChanged && l.start.isAfter(DateTime.now())).forEach((lesson) {
items.add(DateWidget(
key: lesson.id,
date: lesson.date,
date: DateTime(lesson.date.year, lesson.date.month, lesson.date.day, lesson.start.hour, lesson.start.minute),
widget: mobile.ChangedLessonViewable(lesson),
));
});

View File

@@ -0,0 +1,16 @@
import 'package:filcnaplo/ui/date_widget.dart';
import 'package:filcnaplo_premium/ui/mobile/premium/premium_banner_button.dart';
import 'package:flutter/widgets.dart';
DateWidget getWidget() {
return DateWidget(
date: DateTime.now().add(const Duration(minutes: 1)),
widget: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(14.0),
child: const PremiumBannerButton(),
),
),
);
}

View File

@@ -80,7 +80,7 @@ class GradeTile extends StatelessWidget {
child: Padding(
padding: leadingPadding,
child: Icon(
SubjectIcon.resolve(subject: grade.subject).data,
SubjectIcon.resolveVariant(subject: grade.subject, context: context),
size: 28.0,
color: AppColors.of(context).text.withOpacity(.75),
),
@@ -129,6 +129,7 @@ class GradeValueWidget extends StatelessWidget {
this.shadow = false,
this.outline = false,
this.complemented = false,
this.nocolor = false,
}) : super(key: key);
final GradeValue value;
@@ -138,13 +139,14 @@ class GradeValueWidget extends StatelessWidget {
final bool shadow;
final bool outline;
final bool complemented;
final bool nocolor;
@override
Widget build(BuildContext context) {
GradeValue value = Provider.of<SettingsProvider>(context).goodStudent ? GradeValue(5, "Példás", "Példás", this.value.weight) : this.value;
bool isSubjectView = SubjectGradesContainer.of(context) != null;
Color color = gradeColor(context: context, value: value.value);
Color color = gradeColor(context: context, value: value.value, nocolor: nocolor);
Widget valueText;
final percentage = value.percentage;
@@ -166,7 +168,9 @@ class GradeValueWidget extends StatelessWidget {
),
textAlign: TextAlign.center,
);
} else if (value.value != 0) {
} else if (value.valueName.toLowerCase().specialChars() == 'nem irt') {
valueText = const Icon(FeatherIcons.slash);
} else {
valueText = Stack(alignment: Alignment.topRight, children: [
Transform.translate(
offset: (value.weight >= 200) ? const Offset(2, 1.5) : Offset.zero,
@@ -196,10 +200,6 @@ class GradeValueWidget extends StatelessWidget {
),
),
]);
} else if (value.valueName.toLowerCase().specialChars() == 'nem irt') {
valueText = const Icon(FeatherIcons.slash);
} else {
valueText = const Icon(FeatherIcons.type);
}
return fill
@@ -223,7 +223,7 @@ class GradeValueWidget extends StatelessWidget {
}
}
Color gradeColor({required BuildContext context, required num value}) {
Color gradeColor({required BuildContext context, required num value, bool nocolor = false}) {
int valueInt = 0;
var settings = Provider.of<SettingsProvider>(context, listen: false);
@@ -236,6 +236,8 @@ Color gradeColor({required BuildContext context, required num value}) {
}
} catch (_) {}
if (nocolor) return AppColors.of(context).text;
switch (valueInt) {
case 5:
return settings.gradeColors[4];

View File

@@ -29,7 +29,7 @@ extension StringFormatUtils on String {
htmlString = htmlString.replaceAll(RegExp(r'<p ?>'), "");
htmlString = htmlString.replaceAll(RegExp(r'</p ?>'), "\n");
var document = parse(htmlString);
return document.body?.text.trim() ?? "";
return document.body?.text.trim() ?? htmlString;
}
String limit(int max) {

View File

@@ -5,13 +5,11 @@
import FlutterMacOS
import Foundation
import connectivity_plus_macos
import connectivity_plus
import dynamic_color
import flutter_acrylic
import flutter_local_notifications
import path_provider_macos
import share_plus_macos
import shared_preferences_macos
import sqflite
import url_launcher_macos
@@ -19,10 +17,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FlutterAcrylicPlugin.register(with: registry.registrar(forPlugin: "FlutterAcrylicPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -3,7 +3,7 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez"
homepage: https://filcnaplo.hu
publish_to: "none"
version: 3.3.3+167
version: 3.4.1+172
environment:
sdk: ">=2.16.0-80.1.beta <3.0.0"
@@ -20,11 +20,13 @@ dependencies:
path: "../filcnaplo_desktop_ui/"
filcnaplo_kreta_api:
path: "../filcnaplo_kreta_api/"
filcnaplo_premium:
path: "../filcnaplo_premium/"
flutter_localizations:
sdk: flutter
i18n_extension: ^4.1.0
sqflite: ^2.0.0+3
i18n_extension: ^5.0.1
sqflite: ^2.2.0+2
intl: ^0.17.0
provider: ^5.0.0
http: ^0.13.3
@@ -35,24 +37,31 @@ dependencies:
url: https://github.com/crazecoder/open_file
ref: master
path_provider: ^2.0.2
permission_handler: ^9.2.0
share_plus: ^4.0.4
connectivity_plus: ^2.0.2
permission_handler: ^10.2.0
share_plus: ^5.0.0
connectivity_plus: ^3.0.2
flutter_displaymode: ^0.4.0
quick_actions: ^0.6.0
quick_actions: ^1.0.1
implicitly_animated_reorderable_list: ^0.4.2
dynamic_color: ^1.2.2
material_color_utilities: ^0.1.3
crypto: ^3.0.2
elegant_notification: ^1.6.1
flutter_feather_icons: ^2.0.0+1
flutter_foreground_task: ^3.9.0
flutter_local_notifications: ^11.0.0
live_activities: ^1.0.0
animated_flip_counter: ^0.2.5
lottie: ^1.4.3
rive: ^0.9.1
animated_background: ^2.0.0
dropdown_button2: ^1.8.9
home_widget: ^0.1.6
flutter_expandable_fab:
git:
url: https://github.com/filc/flutter_expandable_fab
ref: master
uni_links: ^0.5.1
url_launcher: ^6.1.6
workmanager: ^0.5.1
dev_dependencies:
flutter_lints: ^2.0.1

1
filcnaplo_premium Submodule

Submodule filcnaplo_premium added at 3f23e52748