From d2c762d29ae99bc8452a79fcfbed94fb96b613b5 Mon Sep 17 00:00:00 2001 From: kima Date: Mon, 26 Jun 2023 21:15:25 +0200 Subject: [PATCH 1/7] fixed error screen bug in summary maybe --- .../lib/screens/summary/pages/allsum_page.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/filcnaplo_mobile_ui/lib/screens/summary/pages/allsum_page.dart b/filcnaplo_mobile_ui/lib/screens/summary/pages/allsum_page.dart index c755987..e5132c2 100644 --- a/filcnaplo_mobile_ui/lib/screens/summary/pages/allsum_page.dart +++ b/filcnaplo_mobile_ui/lib/screens/summary/pages/allsum_page.dart @@ -118,10 +118,13 @@ class _AllSumBodyState extends State { void getDelays() { var allDelays = absenceProvider.absences.where((a) => a.delay > 0); - var totalDelayTime = (allDelays.map((a) { + var delayTimeList = (allDelays.map((a) { return a.delay; - }).toList()) - .reduce((a, b) => a + b); + }).toList()); + var totalDelayTime = 0; + if (delayTimeList.isNotEmpty) { + totalDelayTime = delayTimeList.reduce((a, b) => a + b); + } var unexcusedDelays = absenceProvider.absences .where((a) => a.state == Justification.unexcused && a.delay > 0); From cffd93bfe6d5eed1024a8d553cda870557e2d278 Mon Sep 17 00:00:00 2001 From: kima Date: Tue, 27 Jun 2023 18:22:00 +0200 Subject: [PATCH 2/7] fixed auto update and changed build number --- .../android/app/src/main/AndroidManifest.xml | 1 + filcnaplo/lib/helpers/update_helper.dart | 16 +++++++++++----- filcnaplo/pubspec.yaml | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/filcnaplo/android/app/src/main/AndroidManifest.xml b/filcnaplo/android/app/src/main/AndroidManifest.xml index 2877685..1a19772 100644 --- a/filcnaplo/android/app/src/main/AndroidManifest.xml +++ b/filcnaplo/android/app/src/main/AndroidManifest.xml @@ -62,6 +62,7 @@ + diff --git a/filcnaplo/lib/helpers/update_helper.dart b/filcnaplo/lib/helpers/update_helper.dart index 2199124..5aec576 100644 --- a/filcnaplo/lib/helpers/update_helper.dart +++ b/filcnaplo/lib/helpers/update_helper.dart @@ -6,6 +6,7 @@ import 'package:filcnaplo/api/client.dart'; import 'package:filcnaplo/helpers/storage_helper.dart'; import 'package:filcnaplo/models/release.dart'; import 'package:open_file/open_file.dart'; +import 'package:permission_handler/permission_handler.dart'; enum UpdateState { none, preparing, downloading, installing } @@ -32,12 +33,17 @@ extension UpdateHelper on Release { updateCallback(-1, UpdateState.installing); - var result = await OpenFile.open(apk.path); + var permStatus = + (await Permission.manageExternalStorage.request().isGranted && + await Permission.requestInstallPackages.request().isGranted); + if (permStatus) { + var result = await OpenFile.open(apk.path); - if (result.type != ResultType.done) { - // ignore: avoid_print - print("ERROR: installUpdate.openFile: ${result.message}"); - throw result.message; + if (result.type != ResultType.done) { + // ignore: avoid_print + print("ERROR: installUpdate.openFile: ${result.message}"); + throw result.message; + } } updateCallback(-1, UpdateState.none); diff --git a/filcnaplo/pubspec.yaml b/filcnaplo/pubspec.yaml index 3e9cbc2..183792a 100644 --- a/filcnaplo/pubspec.yaml +++ b/filcnaplo/pubspec.yaml @@ -3,7 +3,7 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez" homepage: https://refilc.hu publish_to: "none" -version: 4.1.1+215 +version: 4.1.1+216 environment: sdk: ">=2.17.0 <3.0.0" From d79045e47f3a036265a628f41f078cfe2c90efdd Mon Sep 17 00:00:00 2001 From: kima Date: Tue, 27 Jun 2023 20:38:52 +0200 Subject: [PATCH 3/7] modified personality requirements --- .../lib/common/personality_card/personality_card.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/filcnaplo_mobile_ui/lib/common/personality_card/personality_card.dart b/filcnaplo_mobile_ui/lib/common/personality_card/personality_card.dart index 888170c..a6dec8e 100644 --- a/filcnaplo_mobile_ui/lib/common/personality_card/personality_card.dart +++ b/filcnaplo_mobile_ui/lib/common/personality_card/personality_card.dart @@ -169,13 +169,13 @@ class _PersonalityCardState extends State { } else if (mostCommonGrade.keys.toList()[0] == 1 && mostCommonGrade.values.toList()[0] > 1) { finalPersonality = PersonalityType.fallible; - } else if (absences.length < 10) { + } else if (absences.length <= 12) { finalPersonality = PersonalityType.healthy; - } else if (unexcusedAbsences >= 10) { + } else if (unexcusedAbsences >= 8) { finalPersonality = PersonalityType.quitter; } else if (totalDelays > 50) { finalPersonality = PersonalityType.late; - } else if (absences.length >= 100) { + } else if (absences.length >= 120) { finalPersonality = PersonalityType.sick; } else if (mostCommonGrade.keys.toList()[0] == 2) { finalPersonality = PersonalityType.acceptable; From 5523a2a919dfc17a31e0ab575c2d9b79d5fc918b Mon Sep 17 00:00:00 2001 From: Kima Date: Mon, 31 Jul 2023 23:20:30 +0200 Subject: [PATCH 4/7] fixed lot of ui things --- filcnaplo/.metadata | 63 +- filcnaplo/pubspec.yaml | 1 + filcnaplo/test/widget_test.dart | 30 + filcnaplo/windows/.gitignore | 17 + filcnaplo/windows/runner/Runner.rc | 121 ++ filcnaplo/windows/runner/flutter_window.cpp | 66 + filcnaplo/windows/runner/flutter_window.h | 33 + filcnaplo/windows/runner/main.cpp | 43 + filcnaplo/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 25735 bytes filcnaplo/windows/runner/runner.exe.manifest | 20 + filcnaplo/windows/runner/utils.cpp | 65 + filcnaplo/windows/runner/utils.h | 19 + filcnaplo/windows/runner/win32_window.cpp | 288 ++++ filcnaplo/windows/runner/win32_window.h | 102 ++ .../lib/pages/home/home_page.dart | 45 +- .../lib/pages/timetable/timetable_page.dart | 115 +- .../lib/screens/navigation/sidebar.dart | 158 +- .../lib/screens/navigation/sidebar.i18n.dart | 42 + .../lib/screens/settings/settings_screen.dart | 1280 +++++++++-------- filcnaplo_desktop_ui/pubspec.yaml | 1 + .../absence_group/absence_group_tile.dart | 37 +- 22 files changed, 1812 insertions(+), 750 deletions(-) create mode 100644 filcnaplo/test/widget_test.dart create mode 100644 filcnaplo/windows/.gitignore create mode 100644 filcnaplo/windows/runner/Runner.rc create mode 100644 filcnaplo/windows/runner/flutter_window.cpp create mode 100644 filcnaplo/windows/runner/flutter_window.h create mode 100644 filcnaplo/windows/runner/main.cpp create mode 100644 filcnaplo/windows/runner/resource.h create mode 100644 filcnaplo/windows/runner/resources/app_icon.ico create mode 100644 filcnaplo/windows/runner/runner.exe.manifest create mode 100644 filcnaplo/windows/runner/utils.cpp create mode 100644 filcnaplo/windows/runner/utils.h create mode 100644 filcnaplo/windows/runner/win32_window.cpp create mode 100644 filcnaplo/windows/runner/win32_window.h create mode 100644 filcnaplo_desktop_ui/lib/screens/navigation/sidebar.i18n.dart diff --git a/filcnaplo/.metadata b/filcnaplo/.metadata index ebe4e24..6b7daaf 100644 --- a/filcnaplo/.metadata +++ b/filcnaplo/.metadata @@ -1,30 +1,33 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: 3c0bee85b8e43b860877922bdc411a7333db4d32 - channel: beta - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32 - base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32 - - platform: macos - create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32 - base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + - platform: linux + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + - platform: windows + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/filcnaplo/pubspec.yaml b/filcnaplo/pubspec.yaml index 183792a..5cd1e2d 100644 --- a/filcnaplo/pubspec.yaml +++ b/filcnaplo/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: flutter_local_notifications: ^14.1.0 package_info_plus: ^4.0.2 screenshot: ^2.1.0 + flutter_staggered_grid_view: ^0.7.0 dev_dependencies: flutter_lints: ^2.0.1 diff --git a/filcnaplo/test/widget_test.dart b/filcnaplo/test/widget_test.dart new file mode 100644 index 0000000..a4b75df --- /dev/null +++ b/filcnaplo/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:filcnaplo/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/filcnaplo/windows/.gitignore b/filcnaplo/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/filcnaplo/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/filcnaplo/windows/runner/Runner.rc b/filcnaplo/windows/runner/Runner.rc new file mode 100644 index 0000000..51b3032 --- /dev/null +++ b/filcnaplo/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "hu.refilc" "\0" + VALUE "FileDescription", "filcnaplo" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "filcnaplo" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 hu.refilc. All rights reserved." "\0" + VALUE "OriginalFilename", "filcnaplo.exe" "\0" + VALUE "ProductName", "filcnaplo" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/filcnaplo/windows/runner/flutter_window.cpp b/filcnaplo/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b25e363 --- /dev/null +++ b/filcnaplo/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/filcnaplo/windows/runner/flutter_window.h b/filcnaplo/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/filcnaplo/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/filcnaplo/windows/runner/main.cpp b/filcnaplo/windows/runner/main.cpp new file mode 100644 index 0000000..2847838 --- /dev/null +++ b/filcnaplo/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(800, 720); + if (!window.Create(L"reFilc", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/filcnaplo/windows/runner/resource.h b/filcnaplo/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/filcnaplo/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/filcnaplo/windows/runner/resources/app_icon.ico b/filcnaplo/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..49c0c16b1a866dacc4af8c0864b25b0a60ad6998 GIT binary patch literal 25735 zcmV)#K##uw00962000000096X0C8jh02TlM0EtjeM-2)Z3IG5A4M|8uQUCw}00001 z00;&E003NasAd2FWH?DgK~#9!?Y()tWo3Ei`+e5l=bW0Ws49vA3M>i`0c})7MrQ$y z>5gXVzR7L7qlr<2KyGfw3^#)&h$h`>O=!){r*CqTPqcHBj*_UY42lTIq$rWO1PZF4 z2B<2ixz0IzJ@=2b_t|@`^$yRw_9-}3?B%EA>@~jYUGMMrKJzP#wmG*g{_sCD|KtPg zd0-)`+TE+r{Zk~*P+6EOOF>rv$AGQ|jzu{JbQQ1y)n&j^g@vfj113Pnfi_ATRJ!+8 zRi=Sy&`IDRst15Qz%EpuQ`io=4cLmZ8FUNiR$x2uJgR#Y$@}e8dJWdck4H}4$n@X+ z!sz49u}wx@@ZxR@e(7rnIf$V70M=E>TAJk@76C_tu0dG`oD8}aSc9@k(W8Kcp!2%G zmNE!?e-02yZyVS*^JnjPg?I+FF^T|ZXdlN89rz5ml`67y0{8Z3asd6UhslXc0qfizU z-%JOv=mT#5ZzlohAQ^-Rp!iz@*Y0USe zkMAE1kP!hKdE5LCT*tTy)6xe^C(p>FvjkWRdKPdFs%KHm@GF4{^#H9uD-VFw1x_6} z!$bhCV~GUdxVHPpLEu?ceH6GCxEu6dlnou#T`lRqM@QO><&#W(@dKm5Ga`V)b6fDs z*P|^Z>tur<7)Lol;WeNa3cOxbPX$&0Srw!)0&q(JWdi8$YAONLQ~(bF=)i9Ovj*Cd zgKkGz58MTMEAV~LjjBxbF3hK<$l7i4FOA4xL;!PqTlAqDnONH9;Lc7LR0_fZR8Irm z0K5g&*8s;UIz~Y3)`-qtKF58JbOP`^t408o=T^x`J*4F&H3gUgo<{W^;9Cl}fIf_} zZ>EittB~<+BQh8fz}(pu{?heGt5u9)>5Xt%0D8KjZ$r5l^wq%162Kkcr~=C`mU~nH z&-rcwNFbXO0;m#+qg{%hAO_TpXHk9t+yMFw;K70n`uEPs;o1+42H}VRUifX%hrWpj zUCX_K(ndHH)r%G01)PoQ3K!_q1q>%h17Pvm0Lld5w*dqQAZi0hkN~P=gpeh(;@DHb&5C{r_%^EBN<<)xq=FFv%s%j?(*?dNinKt_1pNt= zcZ0qX7%zj|PXJ|5ssRjc0bbXF0vJXG7={2ML{bDS-H+iUH68(kiHwsJL9ek4puJw8cUI8gUVN$tcl4SOR~w zUz9z-t)PFSa2>D%rI5`gbwmUs0*CauAB+EOQg-rrLSccc6@!nNf2HbltwwaFJu z!IwWW8tfwi=x<9dyA9Q8M6#ZUdNk-yp}Y@xEoiG4YjF#nDI-lRBXH0DlR55!G#U?YD@+moFa?z)Sh%mtKAcnoj|Osv^Qs zs`?(_1EA-iWP<}RNq?;Y(&gj#gDOq{FGLie>4@1kn4x9Q1oA ze~$1aa@|=Xg0EaLB7m3Dw(PQ7$dPK`&O250GT97>Qb-8Jk*~hAq=A*(4WxdSAb9c!9*GoehIN}Ll*@tiIn($`A^_M96 z^C(vUr&&dAO)+l8)PvOLC3^tupweON1BWaO*wt1S+4%d=A4S>!J&=L?Fz9EbAcI}K zbH_F3z}Js#B4{1a;J@N_I^>XL%+n6jl3fIR8n_HtRX$rCchJZa(ymeK0gb<#Sf4i} z#ak}dA>PA$x;oAgOC#JTR^DqRMV!ZL1fHB2Yd_h^J9M&)?BsW#Kl=KJ01mfp>E++) zetQ8}HI4iy;E#b2h__ZCOLCR2;z zB7sccRFog@>1Jn;1&M||9*jjfD$|a^?pvzU8s9KnkQ2Eh(!8--Yn?H$27lbicA&A5T?I= zWElyMC<$Qc<+n5A-Gdd1{5tSQD6gP8c$~l7dxR!4jqA=Doi!y%{I|8le2!YLOlU~= zB%OseTW2e6*!0T2wW9LlIkEuwp}>C8LK?^)J#wUhBSHdLdd1!J(!+C<3^^C{;|dqg zRLLD29$w3JEFa!+F1CF7Isq7pfH;m1p9R?eTW29c(A|&qc7-H_qHt0|6lJw<%yA9< z-{K%Xsj`R844xZ+-&N(#p6=KZ<=_!uAUy0PfMr+Sj%r@0`oe@DzX<%9!fWdU=L1A_ zFyID%O?IT2ov_iSvwk0`{#8MRG;V|X#0%$fks;quG~7amaX~8Jr#@H)d*x){Qh|K} z_knhDf%dw0kUx6ehyY%!ZRr(#ksml7_@u&bbhGyYLqf40kBi9~Fu$=|8dx1>9CUkmRWN3tSs)2>6ob3nP6CiHDOCI!OMy!S=7WA0m4kxPT6YQgW7m%eV79lVSKfh$ zf(i&HP(<{72z0_31*Z1d**R>~e(vxFIz!-2f^;Gk@^PJ{hW2O>4)*ufS+kTjIL|4@ z*Ed(68Ki`9fs0UC3wk%OD{JSpPI?EO!-ND53j$bn<(&vpq~{A~q5K8#ubElhHWrCj z8Hy$h>u|?4?Rqtc|G_qk@6O`xAdPmwtbkOFmv}u6$}F|Z2V@u6#n~ot#)u%GucGie zeP8oztBBA#`BFNM&3$!X&J)10EAJ?}UULw6E6RTd&Z&=O-z*)Q$J7Qk>HxI_5ozH1 zrUN(*6t%@2&z5U3sy>ME78*nAtWbfgYs7G#IL;c_vle)R2oGtlj|)O;-6eBg5}4Bj zuis{ooGk{;`nNro*m*%Ag5E;@f9JgG8F_d5)&O;}C|; z)IJaLoy5T&Bm{>9P~Npll(&k=rc5UuP?@H+?j3Vl5}30D(3|;%ENizrzX*I9Wp(gl zyWD!#ftfV^h7|vu{`l~q7UMuy!@Gw4|Bks>?P){dleB^3*=jK~5U~WnU3CFACJB}T zZ&jJznTh;BQEi=cDV@jXrX(;Y31I1!cXm5d+lpQeay81b(#NcZp*oQj8t6VSMpOy) z!sHN;P2hS7yjS4&Y{cZ(`#s@GQ(M*7?RDe@#duQ|U>ev%&zs>GT>!jUVL!@ufxLCn zC3BJl<{SYmz4DH3=gPPu9|ie+R2RCyUqLSJb38#L&R`YGn6~=vJ=(_5YPX-vtfUkJ zN-%v#{h)D^%^`@D4hjWvwyU~8{XlkHBNpNifC-d0Qbb(cAtIgjy16F_%qap`+SU05 zCKUNi;I~2Nxxcg;>}b9rXjBhk`l?wJ^-C4e#Fgs1HULMG2$1Uk*I^|aSAPetQTMYY z`fkJO|MjLD?lmTGp@^Vxhd`%2$JBv2LjX&=IzQ-yAioX#rq{<`1HZv&rxEqQG;U*H zu;a}9T#bah#EhEiqYCKK1xWBdLZc_A^mRiZ2MmC&;G)vLHgJI`8R&O_PJ8Vo zbT-Tl5||SN&=>m|rjh-jDqpa_#0 zjj1vDM-nm@<6sY@3fHdjxtOJ@Dz!k*2UJyW7o;_S^ZeF(%PidgT_67%4UtVE5|0F7 z!LcS+z$^_WH+RJPDQJ*X->#(V2k#>);XX)x_vqE(CVlaoIM*!LK({y#_@YPrsN&KPh3qR&h& zSa?r+YLc7ivORl^fK@bIzsh^=p>{;fU`88-ISC3~dX7KXds|mQBtNvI= zs(-+0BVD_}`;?j;S6Lb*QqD?Fa1#$nN(!)sfvw44ueH5<{|VrN*3{%<2oDI*evu@B z7n}f=T=^XkMYILNTY=A&BmC4qAQLnCf}kaorW%<2%}F$V($c1hIO-#_SgEu4@!`U8 z?O^pD_XYrIgU@X<07iBFP~edG#*$hCjursti*hgUxN1)Oq>Jfnxbes(fF+mTP9`lN z1JSd9|Bm)z`z_!PyZh95o)g%(&xww{*@z4bR_+PktFfKWo!RDpcasA`8$4Qvmkx|o zQk+J*1|h%>s;J2sru!&M1zrc-CeqrPkw24tOwQsE5WIAU^&xNDCc3@Y3*E?IPovJYp zfbZTwu|n19z&C(hAhcgVn?S}3wDoHEp)5i9B=Dww50%|~b)7#1>WIHz2cCgP{0@!Cdd%2;s@^5$_7Ai~kxa3)Wy?T>mc_0L%OOJI(}AQN0;N z$JSoV)Dwr^D0s*TVA+-58SJX}0)GHHQU90*@O!Ta5rr?$-#^$ICBH@tl-3v@FOj2uM?sFc6NQoGr*kkb4nm zuf61udj<|E0W7=ncJe$!id8!<06q)4GH8MJ&64JsUjs1#zI&EuZHbN61mmE8=61hh z+|iNf`;R5}Xvih60m5mV-lDo60O1iZvdY)kS7uXT=|{uHXH{%8@LN)qbN+g z?9EI*@y$bq(L-tj=+^89^mx!ufu2w`-j%+WPv0+}6#n|2k*cP^p%j09jEEvIO%jtm zqc|4T*vR;oSPQXs#OjsD-sT}QBwYbOvFN|jc+(gmG{c6cA5KL16vaX}|&;#I5e)oLqY({uDrbn1s!EZ}DHWjLC zen_ZI8j~S3K6Lz`3IjX%JyNdofIXIPP`~*rLAIdT_e65WPkb9wPkv+8v3f{UfV`vR zs)%r|!fyi;Ht?$h%#nD2pw6$x`jsg{-ZjzepWISdq01_S(zT5&^I<;fyObGn8NPcc{k3*xa&$}s70aapbpM+dAeWY~Xx-UXo5u(W3)1u z>g4R&H_49O2iW%fes=6Wz`g@h1vv;^4-{pwz8e1Pw6{7U03AVFRgaN3-k?&-tWFBv zh(iTF-fDF|*6FlVQ1;yN8*`5UmR)%}n$LXXw*r3;tia-X_Zj*U!LLylVo2XKg5P;R zhs~k}Qa?a62%Jj4s3XNz6M$GU{>6fv)I-7=XjlEXi2%CS=iQloY%JrXV-|DXX~%H> zX~*!Ym#tvU%7rXjIKlj}7Fm`dQoQeUuIxWJ&5qp%*syIkKX~jJZoU6$zPo-KPi^0W zc5)UoAfIOMcYb?r5x|nmZ=)+X9j)*W zsJ{(m{?w~^`Eg5W zw`SMkJ#W0l87D61-<-FOW0x=Bp^ZD)x%)s-l*Z9~rvb1k{yVS3a~VvG@|>L*@v$iDMzi zYTzpf2PaN=8>&qhVvl1GTfrZka4msD29Z4`?yIRW;jRq#aV+mKB0UuS`z$?>CbPIM6H+m_H83EzfxUDJ|ae$~LDRpWzZL zRxFvv)$hBAl}qMx_5XJ_`wvVxjg5T?!JkI#dTaa4>Khw}HS*kPPK_fp@vyWcGlcIO`8QFU8s)y{{`?At?Gsl9D|4ug|anDWyqp5i!GE zgiXA+^hZzj!2g+FynuiAhLfEmAj`^5}>!-5|#_=6IH$>@hSBsjV> zS7N%a^ZncpHiO%HKUeHS&)>dV`Kz0z_=_8+c;xAvj`o3_vh|4J zz$85URL&jiI|#6Db&IODk!8Z`Pdk?9_Z;Nzhqp!n8cg&F>i-U@Ky51ymG<`u=M>;} z;0f~P2Ej%Iu;e4R_L{W-_#@!8mN@oQ;MWazm2+l!<`0Y9F85a$lKxCTwe^$6$W#sI zgkg?lzeNQJg!UHTUl0WU`pr3ieC;GR-aF0YG-TQEKI)!yu;&2W^H_&1&nc&`X|Zsk zeCKv6OJgjR;`u zb^ig1fQp|`_)jQf=JZZuez~eP0R8|to!3O-0ld#s8z?jZ_ipI$N7qiWVQWt75SOSGflbdUPd=OT+I4kF;MnC0$ppS}&y(F$@;XU3t1Y01 zs@~ujMOEQ{7SRI}Yu?J_rZg!axyuiBmh>{S-h=9eVW4Ow&sA|GzIKzjbSUs^5d77& z;#lTjYNQ|WVj2dZ&bsFz81wJb_AM$#ge_F*XczqNkAwe-ZN<$0LTp(Ecdqa77uQd+ zf3kl3e}CKQT=>f4>2z|F5iiyiDaXV)Hlt6!2zZYmv}8QFKawPX#UH+zb~Z`DI`SvL z32s2zdqLHpk4AL;FkyrTw1xs?tT6!r5V{4QKy;#ENwa7Zya7k`8%5QFLE;O3>;C?Z zb`5@W4E!@t64#yH!CK6I7^+Pw5R2ynSHhAr zU^U7=Dfzy6C%m15o9w|sNnO84W;*L9L0?;&<()I3duCVFfOWrePgh}(n(?kGacGd3 z)Co^@-rwVo4FlG^I9{`v9TRYgK2s9x4wj-Lou1A=N5S9QraJKVw@tBrbG-)tJI-0l z>tAsUd9J~C_lo)^$sy6U60$eIIRZZ=0#ltd1BzJ!SoAC3VyYO_t0?0A*ED%4O^O9@ z>OGV1%ADnf@!2PG{K|zzeFCca4DS9B&-f1v6#tG8_}$^g zuLb=#0DBfcYwrWgNsB*x6Vnqppv!^x7C}hHc#k7R8f|B3lc1AF9h2?q12V^RAvf0+?CX4=x2>U&BHV>^l#>U&jntSr*UgHg-IF$?f}fYjR1t^+Bq%xcIu=s;GP5A5zkKS?EI`8mOF6wi%O zfj;g`H9~gs{O|@pg&OeGj-1D~*42QsPF}@=d1D?)E#Ci)-G+_MRS?Dn-Yc*Wg=Odc z!SDnyEnNr|KL3m2tW=%pjbeUf=I)=R9s*GhY1E*fAo5$|0qUT3(qOQ_2k{&o9}qoK zgc{EAgIk@=_o*~9K|Y5u_`!@2uQueF#s9r@c@<1ivZe0&l=FRJ!!E) zqo0e&OlpX6u&LqnVCqCSpI?<@de;664uA0Xwr#tVsfsOd{zRMQOXfKX1U=IZc*=nH=FAv%{OUdH5ROO- zjsc$>3{)2Tqfvg|=Z3F~y6~!?jgA?I+t2d(D)&Fq!4Ei#imDbd4!!BtwOE)v^tb$t ztVLJjs)Qo~vDo!W;Ek~cWuZq4(xzC}d))gns`Y>PGzCW-Kttebttf}ZMxdo;~WYIiGvKR@EZufY5XVZ2>NN+(zL?w<+}t!<{a zrd5dJzUm!QQPLO(ei9AW{d9h=#lQTGUQo}ez?-~acL>7nS>6ZwhQN1{NBG*3mEUU| z^ng2L9JGR;7#Q(C-`h^RMPFU#X!x9$!pz^>RxB3ADo{BvSu8guxD9=-F`E`O7?0_2 zNeb0{;tw=?6L2cPve*BCod9+}a9vNlz8KZx@#N||J$^o8p2=9KUht~7E`Y^lXAt>= zrUgTgkL!0V`Vb2-sJBi2v8ZwWip(SVU~K;p4t@bnTq&i5Ab{ug9OT*O_mx$HTK>Kh z>cPGiY#sqm=3k0E;&|X<0Os$p6TqS~F6q@Adza7fuOXK}5cCcf^wGz*|#DgDTd<%J(=)r- z{`>*|+JsfzUu>Cw&X1_B@8=n7q5IT#ybE*z`obCOd(rBZ8JM2f%#k3JKsZ-GDGRKVZs z!q=RXv1(}1xTQ+e9qAiHp|Px;+6 z(K8^b1^AcE=hN?d34tG2I1k?Z%682K_|&s|xa+|!#gbuuOb+y6{r(aJbpt|i+(c@) zaBdfivOuTuFagZ}z;(S@_%zU0C;9%X;MI`PP)qZ@RsiTFihy;$#5pE4K^@o&B?I{Z z?KZSUW`5sjaJVHkNG1Vw!%)3fdj&jSV&I1&@aMJbTKucEb^ZUAf7!%_EzgtnEQ8)8 z9E}EF1@&~bo!d0tp!>(GfztqvI`=9+0ZimGKi>eXj04^^!-q2k)nzDDizE?vOASQy zxqwg+p)@A)t9k+Lj_^|_hs-=p1N~V6tVYQMyWa2k{ZgFyd)>2if$-Dkk1?+t?aThj zY5wQU4|8y`W2giqfm#5uu^0*T(J0JKWFX4QE~d#RJAML~5(Q-pc#AW@#}V)oqM*|M zf`-Bfxj2Yq&`PJyd%3Q6h3O{F`Za88#D5>k?n|4kLt?)l{LUExrJv9LQUiZ)d)L`* zUc0V7``>=w(|r5hO|*Qozc;R9@H};2i*MB-|0E}_@f3+CU|!w7_$Jnp-`2#R zCMyUTtA8EW#Wq=ctWE$>=!1%0r07@`=o$lhvP)1Sy1mv;^^%Dv+b=%rTfkp`o~jxX ziCo|h1q2Pgh8_IwrnpJsr4Ig=t;+bde>2W0$JSqLv=-`5Nr}@O+-oZVOY^~`2e$cuB z8<_B{_=#dA+=c+jyq=CO5n)$vR|c@~m%i33^0^lD)bbtb+P=v(Xo>*LyZh&Diu z(oz;tL=2f5U~R1qFp@f>?rbmNZG?vIXtD6SV3;=R*8_awX~MoW=tKRWW;;R^{G*xQ z1^!?CZ#S`Z=Uy@qgAs0EpJB{~m;3I2uXy5*Yxeh#n~1^kEF@n9vIoku{jtEhG=RI3 zgeyTh+KbI6vQ!wDXM4&*gMY@4iThAZZl1Y$BKX6kqt4e9JjqHi@Dl^Sj;#Gr@IO+3 ze}@D7-EWp+`Ija}yTGTHq#KoQqTzJnlYwJicLV3^@|ETL})T^@9ccngaf{Bk()H|IGsYS!U*P zC=#aL1ekEb{ zcMrjE(vLC3HZ$gMV)c{6Wwdb?zYGeB)c276Bm`VN+3SVuRJU~ioQ3-}G;#Ut<=!M|f~KfBKb=!ov`+RQ~eWBDut z#1pUi=b!8(a9_u^0M&J<((cZ|2LFGe2;dNLm9lK1miue0^UB9G!iu&TzX$%5>mr^B zL*R}-$j1-|BYk*0|7d& z+25u*Xgn*?eeGJ!$=WI`1lE+}b;Gb0BiNH!LnG$|gaEYyKlqFY_(M^Escydh6C*FF zp77q0*dGIb;peaSYdrBLI79+~j*ot^5>E=CQ`*r#wnoqeZIq>;tBQl-8I(ydcF=_y zZV)2|4O5OuY6t+oc1TUvmHX6|{9`L7;&=6p#C{w2T{E!6zQMq^uvX`B-9{RS=uOUz zTK>H%533NCwYw>TM`9@d^HnE|IloC1`Oe!wOKztphm8~Spa{mmO`x5m&@?yEA!yU1dJE_a7iAXq@U1m zA0j$mikyJeZHhGDu@dqm<>n0&7y>hW*r4YFU#vYA>%JfO<#CjtCIqhu4b&0%n}9z- z2lmX~c1bd9`ELNyglnzUh+T)e*Nzngpd3@|(|-D*4nZx(PECvNw}9wS0KW=;m*kD-|-o&_4|C8eJ(Fqc=q?l2P7r^BoN=g<5st4JOGU- zizI8l8`GNl0Ml&mzICE!KnI1P1n{To`;5Tf4E$w3zxZR{-2gOlx?P&fpq+k0w=5hlmKI8aHnt6GNN=z8T;5Ad8{TmRUFg ze**ZkvYkqXHT=c0{gQ^VU(NIdAxQ|(l^aRSqY=7C{j?=*V39lX-J|^;3NL|L&rLKE z2EpqBqYK1pY)7id?*jiw>`w*1?|Yh4eMwumBW%3$8T3+&c?nXh$DQdjP&)Q6YNI+Y z8Mt2XB`|{-^u;;hZ>Wj))$oiT95jI}z8I-Q!9S|r>jwX~2Ebnds(H>3_l&M~Pk{u- zTk7xhD86LlVMuK$j?HV66a*9~7!m|h0=YPjo2>ak(AGa_nomCr;^1Hkcm_t`w}OA~ z0Qh~PzXnr%-NryoTi?%Hbi?!flyY5!V+edP3;t)tB?*CvHt2Zz=d)((X~G>Fi1(fs zb6yj*>_^BWXyJEV8&)qmqqX{m0)N!+tpm`>VfD=V;N682B@f&TrGPLxiD;`2vYS zXpwZ^CaJ`BYy@WiGEuhM^s53@&NdVWFgMlh7SYs~vx44v|KarfvRTyk(ZXuN2>cCZ zeitBPqJMPu^MF6W_wLfE#Yr9R8%gNe>bQ%|{_&_kU)$U5nIZr~XBq^;eS`5o*3{rx zhyw`TFCczJf$w^F`8pYazZv*70)8hT!+95yV1f#Z{;|L|HL4E7r~CS%{v_;j6qESh zAH)vhoKzkbO(n3p1=ZfNp=NvEhjh@0Ha8A-bhK7qlGyM1W}fbkyX>li)&U?jhWplF z=bF)d;l`TOekWN31A8=)s0#64UC*h8(|R3z-aMD4^#hFbtX0Qjs@f3D(`}Ht>nDwT z1pXxO$Nku@zQ<5zD{W$M`u9z##;)JKbl-$xq-KoRk=Isbnj$sOOf>Z)P5l4X7ttW* zbckf3D0scVH-Nu;U7jnej~anL1pX^N`>kS$?m_T-XR~s5r^Nb4lXD^gKbUL}cEexN z?B4w?-oT&u?C&7Z={5`!KysL$t@q6$eF&h&(xVM$d>in?uDzH;?0Pq0s2G3{I5kF0#+m@`dt1XU!-V3p z5sbi}4t^i-e4ip-W3LbXba6T)fuxB3Zc+%w`zN>6M;`uK1KmTDZQx)W%!4-@cG)ns zVAFAKO5jeIRE9|w@|qi~;2-Jy4ZyE~!R!G~G+NG|7$X>K{FyutF7O(Y5Juix?j) z@ByOkK_>~>q*0&0F-o!Dri8>KjkHOv*E{>`KpWUIdqY48Eij_$Iut2LZn7WPEc$A? zr>$s-IzHme%s&Eu8u(*HI0w!N=FMAOej)G$C5m*bf0ZmGCS5osnBmgx1EjF04eat) z0&x8fH>BrNRA+~4f`E}02ViSlYRIU1xX194zpD-UTv-mcIRN8=xXEVU4DjC5J<>!n@Eza}+@nA9pEm-30{A5w z<6r(1Vgd$!qn}6gH`LCn-`6F2hX5G?qgbN;Vuelut33& z{Q^FJ4S?TH3ftR?ZZmz0MqnQ@o*-qGZw&l^grgdie|q5m%n1B$@Za18{|*EA0|QnP zhD_?fb$TC6bq2!HsD!|LD8ObN_vLL{8`y%ooOwcnb=a0Cc^@?CD>j?af z#C}&FtC19lN5q5P&iTj0a8lv$9!? ziP5y4bkNrko^ZeVhZuo>81P$?!F(cqxTe1!`~l#mCeT>Q(wWZpw^w&o1!e9Wv;f7Yp!HK_38qe8zW64V^q^^-&8(;E##@-kG1k01gG&gzlq} zML~>SA#<*Py}Y;(H!NDlxJmar1&5a!1BR;mJ)(bb=8OjknHKhJkPOtU#(&u7@9DQa6e@sj z2X+FhqX26x9H>{Z(8vo#fo~Ma;;G7EJ3*%#+y9yOpFaYB0{G2?-xag3@i8c|{C+R^ z56x{Z`?vLK+Sq4L-nw}H4 z74)k2e&&56@VmCZ@8$DX*Xr|up#k_)ihHC6`}p*JU)+JkMHuz@n~H0A;{846r>F!d z+Z27iEh2kWbyN9<^(cUT01u5WD~s1fjEFX|a04cp8guJDsm-aFmr?|4V0hPi21-iya)>J9g4&u~G!Ew-+7%+O@5%>*3KA#N*`PjjqoZ%Pr)cW*(o4v>ucc31y zM_hi^Y()=YNW-!0xPF63p0~TFKT5?;H&Y_5#00bLCbXs%yT$ubIWTJ-LStuj@|@Mn z7L353Xzlj`0l$$g=<9b4Vz1sG1HX8H9=FJwN_!Kj?`Ij>ro}-LM1Q~Z@fbk6o1*s^ z%06JBw}DM+{gt3<0lcjuQ8-G^0{pOBs3_psb@h$F?*;#s9d)r^d;{N<-50qg0RH%L zUgZO}5@U&xbupFGYxVclC5WfT+rog0V*~s4^$DOjwu!!MUhMht zc4#+JDp%#`CG+_7`_AKMFI+bQe--=#V!zwq=NO2VIQiIY8}Ri9Lv1xSE<&IY>ZQFs zHTGh){T)rKfj?B^&x!#JxZC@e?f^FR2>_lccIcYMm^W(%#(BGmu`tnQP>^ze(B-Nu znm5K(KmR)Z?L{N-*TCN-!Y^^=_kSz5WGI0#;9JpWHbnnmi7yxUHKku{bq8FczgMCv zU%!dM^G8;krF%fv&s zKXwN1d)sLv@Vmfo&FXUxx(Hap<-ElrCdZ6^xWO-2R~7PG+^uyC%iy);!8G%B1le`H z=Illxn_jaYlpN)Lo63_8a2Fh%fR7sT2L3r88E8lMIqI|>D0!aqmRB9iul>ZUoJ&%T zz#oY4vxN9+*w-%s@SDbElIOrzD!V_1yy3sj1SB5`8AbnCe;QMbeI(|vcib--x_f#P z?beow-W5Nj$O%YTE<8d!G5|gwmzZgjMk0Q=3Q^aGpvua{^Z2cwdNnH+&#RsFz^0r( z9)Ujq{zztDQ0$KwYJ=sxjqW}gEC*%*o~7DwEY`nfe2i2X(v+w}=l+ABKSYHkV<(bz zzVHjZ1M7ij-Fz69{AbSV&E_*Rx*BT&IGzv`ErtLvrV1TEjp9|o-wxA+I{qlArR9`M)o{#Z>y7h!~o zhejqnOHtG~X*~7U)?mt1DkSMBZFTV{0WIYQC&JEyDvEKEOqkKJAS<2Cq3FPCDF&L5~L>k zMDr~GcnoxN??=`H0eQX$xHlPmQDjzU+?3fpB;JQ+`l{u!ddvO~&6T(RtK&Iq?ef|q zZh3H;n|?Tr2uI8|O!Zzd^6TKtFRo7;x>nVe-A5TfwtD`q!k%%^q!#N;S=b}q4xa>~ z$!(D09zSTeT6=nB?z(XRf(i)St&sPKAr9`|D>Ny0)F7ZpW1&K&sXh8kRhSrW@$Pfi z(ymPLyZ0+!x?`IClN>R#|Il^&J+o(8DK8Dr{Di1 zD2nPI^4sZ3`K?Ydurq+EzyF2aeKw%1k9zuoe)lx@VEJsv;#AnqyP=Su{aL6I)F?$2i5ZpNjjn$Y>QY3 zqq<+?;J0tlnuzOBHlVQO!H@L`V1`AtvkSNjGXod_UTTb%_4k2*9QWNKfOiHU`JY=l zoC)W?d{s^S-@aRUa8rIn=>Ef4`}>yuDiE4w_4$D26JNN)q0&3(1VKn>39-u^TJJ_YpkI0RK?@%%AfcS>FS z-|$S%w&!!Q!<+z;!JiGx{JvcN$jt8@%wE81L+~3sNv;H+V4_fy(q5q$fl!FDZL@{@ zDhBmq3gy;nx6Tkir!%wf`@qw2psNF9<;XrY!89cXz#w&aK;LhX&O{)dc=RG(e*Dtf zEgsyIbKo!+|Azs;vwwYla|8Ir^SQ*bd{+YaqvgB@-p#hOcyMQhECb-j(f&qOe{-{3 zCp`^(ABC+Ce7sHoT>A?>k$j`7e!nN4*JkjZ+fA7XtM#P~l-pPZxyMBpv@Q7fqYV0b zJ6r==Icx0-j$Sgqw0o)p4{YqDuW53mfL{%Utjdg2pJDqTOD*Kt2Twv#4>d&0#l=o{ zuunkr7faI4z`o?Z2({NBrBq1h9)*q7Tb7jH?ogQoZt}p^*h{J`Q7=YtSmTnICI*-$2_GTcn?H z#h`k7nuhvnxX*E~SZt~?q}(JhRXuqy5|C-6*Z5mNp9NN$^L&$v1*N(K!`Gd3LuRmim7R__uVyFM)28fUX=n=ojDLOTBy;_(3h+JLUv1&FIF^-pDMk z3`PL@XB3xBfLvL-YB8rCx2$%_1DiS=oILWu-ztGW8V3?f3O7b@Z(EO_Oj0ng6!)|*2hM>x+BXbdY$kM zipZfAHrkGXTuhmP^#Vf{iNTn0*c-%M{l6OR_ROxlNclaC#D6>ZZ(_@ieHHNg2D1C3 zLL0Z6`kTLXP8s(=Y}5rbn&nKTLYcZ_I@YqkU5P@pxI*RH5Z0Qna` z(EIa2;CnWFSFHQBe_@bT-E>s}Klog4%zw2_y-Sp33&(lw$#wt#<6D*Od-Sj|09tGK zmB8=mGYMGwh0x5eff{~c@F)AplLC+;;J3K$s_*HIGl;}17{D(vFh__fpn`n2i)FS{ z#D6CN05H}8)qSXb)eUH^>((((T?OcaFpsMZ);a^8ID*Q3)~#N`DXW*%u6|(S;S>IA z0sj|o{Sm+V*>4Si-vI;y!9BqlG34Wq4PK3ahPry9e(~5|R@@UXLp--%5-k4xXE%)V zuePyTWhgD~;Hv`r-h6Pc^LX32cdFHU>J7kClxhUTNvTw?-%Mfkj)`ApRV@ZuqsnX6 zt>mam<)4F7@ZeMV5e5D$uK6aLx9=SQe<+9FD#H4}H3)A1=3BF`3=l&*jfTSNkF=Q| z|GQf9`z^-J1n@)54O%`;;3?1>LAYtcUGUo}0pwr$K<^&w6>csI<^KOw=XcvyRUKlN zR>b;9E%s7t6r`1L-f73wI&ItYIZr$@_w|1p_}6pAHQ(gv?R#m>Ea+kcl{cZM0{+yQ zzv=}rf}dnpqze?GNhKEJuYpWLTau5jzf=AF{(1`P?I@6rBeB zBIxjrF?E660(cDP*S!+GXvMNRpe$cB!E4sl_5Vk=29~XF9d%4YLln$oLDlEQ7C-bh^ck~>Nsz`8N;(vV~>;|Ncqkp-ppF|F??$mtS*J1^fd8MZ<&KUAoJaI8#0`Zs~`h?ivNB z9{^SGn^*90f!JX53)d!alcr0I_Zj_CTz=7PNw^k~Z3=98=wl5CpnIo+nDw6+C?sgx zTv>htdVA3LU-ua}0?WF$v=$vvvUZ{WSGTRtDNjB-H}!w(%zwo-H}Ukddm?M~xd**z zp$_kC=ovbZ0H4(2qA_c~IlkZEaq`y%jJPe0s}H-tPYC=8dViOsHUnR>#K(jQ;M!mA z-SvLpdM7~DC;F2R99T6ox+isY&h&wMR@$^;@jPC=t{(sYqb)hl?VWr2zg_IViOtXM zDJM3mPmJ!1+ciUd zbq;UvX9M6DL+*You%}UE-^?HnWc1B|KLSn{IJ6D`r*$1eIWNb?8z=wqL> zvfdgqK<)v)0Ad=^Q3B}O0=bQDMp5wVI{{nne4mYOW~W*J@39|vi2+`B$|{yD7%v^! zcTjogskw!IGx-0k0)F-B`hl6vn#>aczv!UYk6)B^CG}{Stb9oF&9WvSRaFwN(a|vB zxZZQ{AxeN;{3IOhe|NUwtzE35d3=74pKCk6c5``{1G`cdD0B$-FU#y(L82uq+)?1e?u zuJgOv;skn3H#g=*RQoZguSa4J0(w6I4~YL7g`$+--vE0>BxwAz699!2&;|B&3g7jr zYG&kHz+N8|@k<>ib=z?C()pZqQeFRFzd7gmeJ{TLuM{)?8EZ0r>!&9C;J>90{$7Ni zLrn9c`K=&!UDq_q&yw92nQTJf_s#si{QgF2NU_}wTnCVMY*MYw z2JlZGrgi_Rpt=Lqze2U6RSSIbj9z`4>g*m9>pd(q@NZ^TR51*2j{=c2z%o8#{_^%n5`F)XOsuA~I8T?_9Kj7aF==(w7SkJBK{J|g} zhuAL-qx^i}?}Jl47JoH>U&EPv)|i2MTROmB13Ow1Ew(&#m1*p?&4cunj}&VL!9N2Z z06i!ErQL(7)H9lJZG@FWVW{Vvx{5{f#!9>Q98ey9I_Jfh{PXU2n;3&vo|y5TbH{k& z8Eq!U@Phw}F8Es_f!uFTtiCJCE!6eH`u9EGHWt@!FXj%-D(Mi>;s^IDc%c#kmgs z|LGmdQ#)Q5@xKRn5tugyM=!}ZeNDzir?+_B$t{*F@HgTgzrCJc{cnRoKK_9bwm`># zuko-}4G+lvtRzX`m!$Rv5|UiRWgIh2eEn&*h=k%EGem0={d3?ifo;2YW$f7$*ZtE8 z0N}V)y$<+CME@hoxoNYvl^y(tBdi*xoQjMFp%`07WHANx}@lV?!= z0yx`tk8av->AyA(FoWI~>Y6L3uQ`enR@U|Z`!{x&&Sz)w&mQ3E0)F`-;bli>oUtb3 z^b=Z~v?^oeVqwhK<2!a8;J^OQ?{Uqw-)GOhN%zceh~bY0_4FDa1;0V{vexVO&19C) zUi_=|HUhti*wk$CkLLDcYg1UgD&2D$_;X;}#PW;{x7ug_bOHdhHIrNZCE(wKyg3T+ zQm=*$ducPgf5)JS;hdKr!-9$OtM1ybJTip-KM45oHXOB3IPvI0tUqH-i*?5cE0<?&KTyJc;I7S*}a{{S^|E10sK{WS1$E&st*45J(dm2j-fC37LKV zv|dm9e6bUOaVoy|Pw-rL`s+57FClPndwM=gG6C@AkHEXG0nwenHK6AL^9uzx4)7$9 z#|Cfgo?0yb?b83B+O9mEp#Kj7eyjz{76~V;%y`A|8E2f3aq{YnRZBDGjWr{b=}yiM zAAgqr_iOj@_uqP$XP(2oJ$2>0)7n%eB5(IzD5S9Xd{pB8u{-s-`g7nN$KNIKvn-{Idyc^CU%r}o=3V z2l&y`&vDNqTe<7O&D{IgGd#I~NR@t$X?Mfr!bnk=) z?->^R4V8-P-@JM z-3NGV^YeUv{Z{ULa0@^9(Ka@0*~RmF57NnVG77+FB4jPcT54uJ-v)e%*<@T6bl-D= z%X+!LfeHLpUpk(p#k71?XwlAiW}gxK$%Q?1wv2wDJPdkGmProEmWR@H|KSM$Q0XAK z0NHxb&w&1^`<@XulNsFA3&cCfb5)LAzK}Csw!C)bs-?om|Lr`EUm+}AAVb9gJiqrK zk8ggSAN=SU?tE}F-~Z7z9^3pp&+R$DbSEbhL8Ks+u`Khyc%p$X1|EwsHs;<70mBUf ztJ)He?lXbkRiP;O9+AK)V`iUwEH?!|5%AaAG_CL%5n12q^{KMbg`;%0eWb60WYBr2?2lRxIw`B2EDL!ZBJi7BcK&yC z9iJ`mlc;kSvc^$BdS=k0>iZlihV9)+3 z_8*+C)c{P{_8yqx$!)v&mq(xB&WAShy+^k3qo;PVeb;^_rz(JN^;2`>Hju!83eEW5 z@fx#ASAp9c{AMe^14JQ9zrTu4EF9cpGcb$&p?3_4{f>4gfZveYUnR_Kie4?qE(D%_ zu({|zYXks5OVCb^>eQ{7jDHUJy%d1Yyta1Ta!sX$f$K66p4hsJyB^xYPh4=a`4;;Q zPP6Hm-Q4&1v)uXM7QXw)79QEOlV_jb&%tilpjHL&{sA%!@T&wEx~~}1b}QhYWs%4@XX`*>tBBd>yBOOy!^oAG*3Odm-{z7%Uus`;qHgG@bJbR zY}>hy16{ypBEfmQTaPyh1hc*lfho505l?k^$ zu$lLM>g)WUKXW$co_;LzC)zx>`ydZIv7NggE&%_)6Fb{u=`aWRzM1J%2mH;1(1o*^)m}0(qo3%eQI3%&SN~A8}cHrZJ zJOhyHEF`aB8N-QN_RdciMH56tbNM~scZYgLRD4p?b$PBVm^a4DRxM`H{BgGL+Rx@4 zd)a?r3MD?nTRvdv!4`rD0mU4mn*jbpJ?n?wDM947xjql&%s-r~FAV-h@dpZge5!Ns zW9`uO!lyw%-wOuoJxZf-yzYw$54;A1ul+Nz}f92YM-+q|jqw5v@ISMIY z)p~^URt{K z*arW0))5^1X(%%&j(cdpj%(+1$z@Zz4} zTS&xFRXEANAD@K;e|<~rrSbjhtl8OGhvcq5x{mCW zcNX9l?h`l;I6JBni}!n(K;OUCegjbtfE@$F^<4iY>TajOV-Q^Qu{KcS*8N}@FP>$p zeZcqhv3J(@f!qfUtFaGTy?=s*KP})V5}K=agI}!NTIrqiFIDwwg$^?NqJNEu4hg$l z`d@nwI3D;r;G(8P7LJljV~)6AognZ@0Dk;=5kPnce!Qwuj1*%=KIi*Jb^3VCJPCLl zOyKtwB~8-#E%g*NSdU+ZQAu1jm44se3j90ZDdo_C9}bZUF!LInuAxnN3gx3H8?6Af zi`IiXJ;2m~m1~@*ItpJXwHZ)+mpIlcEY0{qcb?Wocwe8WV~Hz|xL;R)Dg#tqor-_f zr~aNniAH^E@u?fQ$!^kI> zONtklY$Jsw^AcdZ^if?^7{s>5d`(HEy0LUM^ zj_j0oAVm*+4_FJlsyVB~!0!190X^A2_}x3jIk-I6B!J#I%S8yw0{mF!UTit8;H)12 zznFr1G_WqA1N=_Aj3y=Z#K2D?_~VjEl$ibp@LPx+K;Y?z4xQdVxV3qKw&G<4&|Sc9 zP#6Z^WCeS#Pm=&20e$VacYyZ;R3tLs%aSettbx(yj5VSSwyx}y1^iF*FLf^j6u)z z%-SF*jAE8dy=DeJH~2&PE%;Zitqx#cBG2rI{9$0Zz~x&!G->8{8G~mt-M9??jVQm2 z$Q`OvAlVBF{ui780P@GLCp-C405ZuQFWesTHef+;)TBlLngHKFP`W;`_Fy{j8_f72 zu~2PUcV;7>0sMHtPXzp82%EJnCXx`{hh@n#5>k5mB$f*PfeW?+ACau}Rn=)AdqIqT zFFFB0R}xrvDX21;bslJ`>;&G7Fkt~i6zrh^?7Cl)$X+K53+QKNy$C>X5|P!wH*ju( zfzJXU|E%u;t>YVnl4ZOgt-eP}m7*bL{+O%Jx6)5gGU;Ei8~9J-^RvI5o-F+QhuYmY zrwE`=0`H)uLZ_4eAj@Pw#rgtcA;1p<_S*fdz^?!}&8TPfFvJIW6pW3k4!QcmAnteD zY4cXZ0(-13zF7i)%*Ri9?S8P%{OkvQM>YTReCmaB_suy10O&k^9of1|$ud#U?+T&< z7YekkvtUwf zi(qi|g)9Fa1g;X5KS8APLc06rBmqES6KJiw6p8}lcXYe(!cyCeGqp3SFv+8m=(Bf% zewM|2LK%u~QPY&T<9uW!fyYM*fmuHm)kol7jn*F2upKCB03F89@3Tu32Pc6~fP4yJ z3S?G_dLH%!0ML2-dfMwQg<`FtJ5Vy<{C@k?3v?=jzKK5X1y77{O2##d&)XI5>@P|Z z22jArWht_flDX#tL4ZV(MSdr!G_5L>2BJV?0Y0Qx@(H!GLhh5mr&RT75he@p&wg(I zVM_od5>Psz-vP-%FQBUqIKiJ>Sk!GGYc|_EM!3OK(LXQ6Fc}_z)}_OmMDq1XA(7yQ z(D!YinqBZW5Aq>q?LSCSF8^vl4#JBs_RmQIm?43aE&=EWayv>#g!6$hNd|lG%M>%L zsL`N^Zy*d7Gl>Ji4G3GtU%mY#l$W*uzdZOQ)wSmu^buX(>HZ8^%EN{<1NhapPC<%_ zk{;o=AGiwTQ^4e01OJ>OfF22qbx8pDj=&z^0>HSsM{kywW?*-K-X7WJGCDRj?D+;= zT#qfCpO~vZIQXs6eDQk?x7>KBGO=iCLeC)vfB$>z27U*XKPhznxd;9^NdN%Tk6%xF z?Ij50qMf^tY#W7Pa6$QtNbO?}1Ny=2JDK^U+CaZnM0 zw<9Q-r5O4MmHcw|Pe|m4rj~w-loG8qupRjIypw-UL^_DfN$}5E0sxraa6RplE(Q^8 zwWjV-%^n9i4`pf9C*L@sp=1lt=y@){Cwm;!zE&V4>iDrX3<=f}@ELB_hq$3nk`0xP zknBVse>A$!^&aJCYy>_se~iEF3jR4w00jx$K>MUiQEdV8fT-Rp@H!c0?ZKGK zE&}@c05g;<_ogq0XPTHhFAV&8DsPf?KLY+?E&R3=UNSU)laq}Do|C}d)f&{Loz4qd6XUpTjZNTXiH3dvyuL9ma*sFllUWv2#Mfk-01dD#) z5fGtI1$ZRaVx_6W0sMyz0rW^wu-D4{0nf=#C<^m*-AhQ^20C>|67|(?*Te z#Y+Ld#*?Lt?mc_@uAj`D4=@z)Fz4|*!B4Ej*hb*ja@9W*kplvBa@g|l;RF7|jsSWj zFuwNUVu-40CfU89cLJ|KIne}M68!Yuf$w7*@ecN|J25?8KMWK~$xN7lAJOw?o)-^> ziY`1lb4^9R3eNg5|9*rFtyPA6->+P^|AT^jrT-QgTOXbq;k_xF9Cj@p*yg|Kj|K5k{*-#r8C}rUc5Wm4RvX>-Pcc2fw@j(xdwN zKa%PTy4oJDH*gU2uYr#PPxVecoJ9WWcGyV(gWL2IH!yM1+ab?D*bRCE$QFUKdS%A& z4K@Qf;&BF%G+gI>1LemwPB38`hb9nDAJ8^EA5-bFV0}gnS0@gE-(o0@*Y`IB_<--b zYy|x+MXwfQr-*dx_8%U`z9W(V0H!wFNPFGeib?{|LF9V^-vK=lWgSK-$CfML7V+cm z#Xz>7_i(B)EX~^y1%BzdwnbD3+|Od@OI@0)w`17)f<5xAk(th%eFNx+MY4ZV=m^Sm zXNs*4Uwyd4=kQhms@uXheyWR60dxh*M}W(K72&~QK#jHHuyH*BQ*o160#vN*cxoU| z2s9z^41m?yM@bcX`Ugcr?_)_m8oJ)VcF=1S{!EbVsyQHswa8!H4toh;aGQGKM#k5^ z9aKQrKToD_o+kS)uof)>hupKgHTFH-eOJ3i;}5c|9weF?)D-cFmp~bh5s1a`*8v=e zXYde7np816;2)4CZc+3j(&_w_WZAyrj>?w9T;z9+%_DMK^oCCjd~-coR4xNPNGXii zp5q=Th{VM?65V*kz0&g)u!#CyAc!m4Dbc!LCz;4vdDU}_B7f4%Z}jhXzt=Y4bBcae zkZrva=R)UP9R0KIM5o3CcN)1BK2uq{#ed6X-F(@Y&U5BUINP5x~H0o0lp@}KJ9)3Rbk6RhhHe~hyaGz7G3ZOvTkZoSI9pKcn^iQ;OuUBavSKemn4^1 zmSIf4Hv%6#mVGZ^%EaUxEJ@b=2>2TTzfp;?4)9&zFMuxrI}775OifL_WW@au0eH40 z7hGM8hW@}`h4PcY`+&1Sv(UJX&-9U5I0}5VEm2-6fMR)u$)8`lF3}cZT1;d(_g<$G627T+M`bn3Ps;aIFH~4f-E|bLg&HXkSyX ziHKj`Y1Qv*L=hy1%1*PmUq3k-=U`3Z83Ozc>1C2)#{Wy;%fL2(mI%|ivSp4e$}u8< zIl3)5|7v!zizO`SjjH7+mjdq+^*0081E1GoEs4DVt)nwK+j2IKX8aKW3`GRz zT{UAY>~>m9;SAtCqWW%%+JxhF(3cOSloaoOv_q-myMgZ?Flwx#J48N0{ktac#&rO z{{4k=ZKPzs{>-zYz<&z#W>vljd^;0)Mz!n35kWgT0{(~qUI;SyM3Hou&+KZWdMfaC z;KzZpsYMtj%=kmi_F?c_78nSt7OKP{kGyWnl07Yn=^SeYi~+v5cMWzRh44HLc_A_!N$egyc=cP)NC3;Y1M z0r&>04~odX;`NGqcRH}`Fs#EjB7h^63|{|-bXKjUwe5+12P+GJ(?Bl*-U7S^IG(Oc zu?mo87X9uC!-)DmP*=}Snf23h5~hHsQN9n{1l$7pFchE%W!JXnS-f&7TSrE{5dqA7 zGPsHk8Ex`DLUP6sPUw=rMJTTaP6b^d5DYd7el&A#zT>9R8DFrA`&R2dfaVmU{d(Xo z;I=OKH_}~78=$2V3lZd z|GT5XGa`T^kPy!P!xJ&q&T5ZIKU>Q{FY=<&yEteZM~%1S0|%ZG+p&b3Vb;u8p)89alzQQS20Q&47gn zO9fV-979p+>{!ray2P*oScb9$SR^oyLai95NOx`*-+v}GaT;YBm=rh&902yBJP$la zp|5WPw)Fq$R+R0)bExc-O!E@wbP4C+gB`|BZn5o=(d<6gw*MdCWA{r&Q^2(V0000< KMNUMnLSTZnkCXQR literal 0 HcmV?d00001 diff --git a/filcnaplo/windows/runner/runner.exe.manifest b/filcnaplo/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/filcnaplo/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/filcnaplo/windows/runner/utils.cpp b/filcnaplo/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/filcnaplo/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/filcnaplo/windows/runner/utils.h b/filcnaplo/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/filcnaplo/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/filcnaplo/windows/runner/win32_window.cpp b/filcnaplo/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/filcnaplo/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/filcnaplo/windows/runner/win32_window.h b/filcnaplo/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/filcnaplo/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/filcnaplo_desktop_ui/lib/pages/home/home_page.dart b/filcnaplo_desktop_ui/lib/pages/home/home_page.dart index d0cc653..edfc433 100644 --- a/filcnaplo_desktop_ui/lib/pages/home/home_page.dart +++ b/filcnaplo_desktop_ui/lib/pages/home/home_page.dart @@ -1,7 +1,7 @@ import 'package:filcnaplo/api/providers/user_provider.dart'; import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/ui/date_widget.dart'; -import 'package:filcnaplo_desktop_ui/common/filter_bar.dart'; +import 'package:filcnaplo_mobile_ui/common/filter_bar.dart'; import 'package:flutter/material.dart'; import 'package:animated_list_plus/animated_list_plus.dart'; import 'package:provider/provider.dart'; @@ -16,7 +16,8 @@ class HomePage extends StatefulWidget { State createState() => _HomePageState(); } -class _HomePageState extends State with SingleTickerProviderStateMixin { +class _HomePageState extends State + with SingleTickerProviderStateMixin { late UserProvider user; late SettingsProvider settings; @@ -41,11 +42,15 @@ class _HomePageState extends State with SingleTickerProviderStateMixin user = Provider.of(context, listen: false); DateTime now = DateTime.now(); - if (now.isBefore(DateTime(now.year, DateTime.august, 31)) && now.isAfter(DateTime(now.year, DateTime.june, 14))) { + if (now.isBefore(DateTime(now.year, DateTime.august, 31)) && + now.isAfter(DateTime(now.year, DateTime.june, 14))) { greeting = "goodrest"; - } else if (now.month == user.student?.birth.month && now.day == user.student?.birth.day) { + } else if (now.month == user.student?.birth.month && + now.day == user.student?.birth.day) { greeting = "happybirthday"; - } else if (now.month == DateTime.december && now.day >= 24 && now.day <= 26) { + } else if (now.month == DateTime.december && + now.day >= 24 && + now.day <= 26) { greeting = "merryxmas"; } else if (now.month == DateTime.january && now.day == 1) { greeting = "happynewyear"; @@ -81,7 +86,8 @@ class _HomePageState extends State with SingleTickerProviderStateMixin children: [ // Greeting Padding( - padding: const EdgeInsets.only(left: 32.0, top: 24.0, bottom: 12.0), + padding: const EdgeInsets.only( + left: 32.0, top: 24.0, bottom: 12.0), child: Text( greeting.i18n.fill([firstName]), overflow: TextOverflow.fade, @@ -107,8 +113,10 @@ class _HomePageState extends State with SingleTickerProviderStateMixin int selectedPage = _pageController.page!.round(); if (i == selectedPage) return; - if (_pageController.page?.roundToDouble() != _pageController.page) { - _pageController.animateToPage(i, curve: Curves.easeIn, duration: kTabScrollDuration); + if (_pageController.page?.roundToDouble() != + _pageController.page) { + _pageController.animateToPage(i, + curve: Curves.easeIn, duration: kTabScrollDuration); return; } @@ -132,27 +140,34 @@ class _HomePageState extends State with SingleTickerProviderStateMixin (BuildContext context, int index) { return FutureBuilder>( key: ValueKey(listOrder[index]), - future: getFilterWidgets(homeFilters[index], context: context), - builder: (context, dateWidgets) => dateWidgets.data != null + future: getFilterWidgets(homeFilters[index], + context: context), + builder: (context, dateWidgets) => dateWidgets.data != + null ? ImplicitlyAnimatedList( - items: sortDateWidgets(context, dateWidgets: dateWidgets.data!), + items: sortDateWidgets(context, + dateWidgets: dateWidgets.data!), itemBuilder: filterItemBuilder, spawnIsolate: false, areItemsTheSame: (a, b) => a.key == b.key, - physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), - padding: const EdgeInsets.symmetric(horizontal: 24.0), + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), + padding: const EdgeInsets.symmetric( + horizontal: 24.0), ) : Container(), ); }, childCount: 4, findChildIndexCallback: (Key key) { - final ValueKey valueKey = key as ValueKey; + final ValueKey valueKey = + key as ValueKey; final String data = valueKey.value; return listOrder.indexOf(data); }, ), - physics: const PageScrollPhysics().applyTo(const BouncingScrollPhysics()), + physics: const PageScrollPhysics() + .applyTo(const BouncingScrollPhysics()), ), ), ], diff --git a/filcnaplo_desktop_ui/lib/pages/timetable/timetable_page.dart b/filcnaplo_desktop_ui/lib/pages/timetable/timetable_page.dart index b673689..8d6e5fb 100644 --- a/filcnaplo_desktop_ui/lib/pages/timetable/timetable_page.dart +++ b/filcnaplo_desktop_ui/lib/pages/timetable/timetable_page.dart @@ -23,12 +23,14 @@ import 'timetable_page.i18n.dart'; // todo: "fix" overflow (priority: -1) class TimetablePage extends StatefulWidget { - const TimetablePage({Key? key, this.initialDay, this.initialWeek}) : super(key: key); + const TimetablePage({Key? key, this.initialDay, this.initialWeek}) + : super(key: key); final DateTime? initialDay; final Week? initialWeek; - static void jump(BuildContext context, {Week? week, DateTime? day, Lesson? lesson}) { + static void jump(BuildContext context, + {Week? week, DateTime? day, Lesson? lesson}) { // Go to timetable page with arguments // NavigationScreen.of(context)?.customRoute(navigationPageRoute((context) => TimetablePage( // initialDay: lesson?.date ?? day, @@ -49,7 +51,8 @@ class TimetablePage extends StatefulWidget { _TimetablePageState createState() => _TimetablePageState(); } -class _TimetablePageState extends State with TickerProviderStateMixin { +class _TimetablePageState extends State + with TickerProviderStateMixin { late UserProvider user; late TimetableProvider timetableProvider; late UpdateProvider updateProvider; @@ -60,7 +63,9 @@ class _TimetablePageState extends State with TickerProviderStateM int _getDayIndex(DateTime date) { int index = 0; - if (_controller.days == null || (_controller.days?.isEmpty ?? true)) return index; + if (_controller.days == null || (_controller.days?.isEmpty ?? true)) { + return index; + } // find the first day with upcoming lessons index = _controller.days!.indexWhere((day) => day.last.end.isAfter(date)); @@ -94,11 +99,14 @@ class _TimetablePageState extends State with TickerProviderStateM _tabController = TabController( length: _controller.days!.length, vsync: this, - initialIndex: min(_tabController.index, max(_controller.days!.length - 1, 0)), + initialIndex: + min(_tabController.index, max(_controller.days!.length - 1, 0)), ); - if (initial || _controller.previousWeekId != _controller.currentWeekId) { - _tabController.animateTo(_getDayIndex(widget.initialDay ?? DateTime.now())); + if (initial || + _controller.previousWeekId != _controller.currentWeekId) { + _tabController + .animateTo(_getDayIndex(widget.initialDay ?? DateTime.now())); } initial = false; @@ -111,7 +119,8 @@ class _TimetablePageState extends State with TickerProviderStateM if (widget.initialWeek != null) { _controller.jump(widget.initialWeek!, context: context, initial: true); } else { - _controller.jump(_controller.currentWeek, context: context, initial: true, skip: true); + _controller.jump(_controller.currentWeek, + context: context, initial: true, skip: true); } } // Listen for user changes @@ -131,7 +140,8 @@ class _TimetablePageState extends State with TickerProviderStateM // Sometimes when changing weeks really fast, // controller.days might be null or won't include index try { - return DateFormat("EEEE", I18n.of(context).locale.languageCode).format(_controller.days![index].first.date); + return DateFormat("EEEE", I18n.of(context).locale.languageCode) + .format(_controller.days![index].first.date); } catch (e) { return "timetable".i18n; } @@ -181,9 +191,14 @@ class _TimetablePageState extends State with TickerProviderStateM children: [ // Day Title Padding( - padding: const EdgeInsets.only(left: 24.0, right: 28.0, top: 18.0, bottom: 8.0), + padding: const EdgeInsets.only( + left: 24.0, + right: 28.0, + top: 18.0, + bottom: 8.0), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Text( dayTitle(tab).capital(), @@ -193,9 +208,13 @@ class _TimetablePageState extends State with TickerProviderStateM ), ), Text( - "${_controller.days![tab].first.date.day}".padLeft(2, '0') + ".", + "${_controller.days![tab].first.date.day}" + .padLeft(2, '0') + + ".", style: TextStyle( - color: AppColors.of(context).text.withOpacity(.5), + color: AppColors.of(context) + .text + .withOpacity(.5), fontWeight: FontWeight.w500, ), ), @@ -208,35 +227,59 @@ class _TimetablePageState extends State with TickerProviderStateM child: ListView.builder( padding: EdgeInsets.zero, physics: const BouncingScrollPhysics(), - itemCount: _controller.days![tab].length + 2, + itemCount: + _controller.days![tab].length + 2, itemBuilder: (context, index) { - if (_controller.days == null) return Container(); + if (_controller.days == null) { + return Container(); + } // Header if (index == 0) { return const Padding( - padding: EdgeInsets.only(top: 8.0, left: 24.0, right: 24.0), - child: PanelHeader(padding: EdgeInsets.only(top: 12.0)), + padding: EdgeInsets.only( + top: 8.0, + left: 24.0, + right: 24.0), + child: PanelHeader( + padding: EdgeInsets.only( + top: 12.0)), ); } // Footer - if (index == _controller.days![tab].length + 1) { + if (index == + _controller.days![tab].length + + 1) { return const Padding( - padding: EdgeInsets.only(bottom: 8.0, left: 24.0, right: 24.0), - child: PanelFooter(padding: EdgeInsets.only(top: 12.0)), + padding: EdgeInsets.only( + bottom: 8.0, + left: 24.0, + right: 24.0), + child: PanelFooter( + padding: EdgeInsets.only( + top: 12.0)), ); } // Body - final Lesson lesson = _controller.days![tab][index - 1]; - final bool swapDescDay = _controller.days![tab].map((l) => l.swapDesc ? 1 : 0).reduce((a, b) => a + b) >= - _controller.days![tab].length * .5; + final Lesson lesson = + _controller.days![tab][index - 1]; + final bool swapDescDay = _controller + .days![tab] + .map( + (l) => l.swapDesc ? 1 : 0) + .reduce((a, b) => a + b) >= + _controller.days![tab].length * + .5; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), + padding: const EdgeInsets.symmetric( + horizontal: 24.0), child: PanelBody( - padding: const EdgeInsets.symmetric(horizontal: 10.0), + padding: + const EdgeInsets.symmetric( + horizontal: 10.0), child: LessonViewable( lesson, swapDesc: swapDescDay, @@ -264,7 +307,8 @@ class _TimetablePageState extends State with TickerProviderStateM ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -285,7 +329,10 @@ class _TimetablePageState extends State with TickerProviderStateM onTap: () => setState(() { _controller.current(); if (mounted) { - _controller.jump(_controller.currentWeek, context: context, loader: _controller.currentWeekId != _controller.previousWeekId); + _controller.jump(_controller.currentWeek, + context: context, + loader: _controller.currentWeekId != + _controller.previousWeekId); } }), child: Padding( @@ -295,12 +342,22 @@ class _TimetablePageState extends State with TickerProviderStateM "week".i18n + " (" + // Week start - DateFormat((_controller.currentWeek.start.year != DateTime.now().year ? "yy. " : "") + "MMM d.", + DateFormat( + (_controller.currentWeek.start.year != + DateTime.now().year + ? "yy. " + : "") + + "MMM d.", I18n.of(context).locale.languageCode) .format(_controller.currentWeek.start) + " - " + // Week end - DateFormat((_controller.currentWeek.start.year != DateTime.now().year ? "yy. " : "") + "MMM d.", + DateFormat( + (_controller.currentWeek.start.year != + DateTime.now().year + ? "yy. " + : "") + + "MMM d.", I18n.of(context).locale.languageCode) .format(_controller.currentWeek.end) + ")", diff --git a/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.dart b/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.dart index 0815557..1c0f5fa 100644 --- a/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.dart +++ b/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.dart @@ -6,6 +6,7 @@ import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/utils/color.dart'; import 'package:filcnaplo_desktop_ui/common/panel_button.dart'; import 'package:filcnaplo_desktop_ui/common/profile_image.dart'; +import 'package:filcnaplo_desktop_ui/screens/navigation/sidebar.i18n.dart'; import 'package:filcnaplo_desktop_ui/screens/navigation/sidebar_action.dart'; import 'package:filcnaplo_desktop_ui/screens/settings/settings_screen.dart'; import 'package:filcnaplo_mobile_ui/screens/settings/accounts/account_tile.dart'; @@ -25,7 +26,12 @@ import 'package:provider/provider.dart'; import 'package:filcnaplo/theme/colors/colors.dart'; class Sidebar extends StatefulWidget { - const Sidebar({Key? key, required this.navigator, required this.onRouteChange, this.selected = "home"}) : super(key: key); + const Sidebar( + {Key? key, + required this.navigator, + required this.onRouteChange, + this.selected = "home"}) + : super(key: key); final NavigatorState navigator; final String selected; @@ -71,12 +77,12 @@ class _SidebarState extends State { if (!settings.presentationMode) { firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0]; } else { - firstName = "Béla"; + firstName = "János"; } List pageWidgets = [ SidebarAction( - title: const Text("Home"), + title: Text("Home".i18n), icon: const Icon(FilcIcons.home), selected: widget.selected == "home", onTap: () { @@ -87,7 +93,7 @@ class _SidebarState extends State { }, ), SidebarAction( - title: const Text("Grades"), + title: Text("Grades".i18n), icon: const Icon(FeatherIcons.bookmark), selected: widget.selected == "grades", onTap: () { @@ -98,7 +104,7 @@ class _SidebarState extends State { }, ), SidebarAction( - title: const Text("Timetable"), + title: Text("Timetable".i18n), icon: const Icon(FeatherIcons.calendar), selected: widget.selected == "timetable", onTap: () { @@ -109,7 +115,7 @@ class _SidebarState extends State { }, ), SidebarAction( - title: const Text("Messages"), + title: Text("Messages".i18n), icon: const Icon(FeatherIcons.messageSquare), selected: widget.selected == "messages", onTap: () { @@ -120,7 +126,7 @@ class _SidebarState extends State { }, ), SidebarAction( - title: const Text("Absences"), + title: Text("Absences".i18n), icon: const Icon(FeatherIcons.clock), selected: widget.selected == "absences", onTap: () { @@ -134,12 +140,15 @@ class _SidebarState extends State { List bottomActions = [ SidebarAction( - title: const Text("Settings"), + title: Text("Settings".i18n), selected: true, icon: const Icon(FeatherIcons.settings), onTap: () { if (topNav != "settings") { - widget.navigator.push(CupertinoPageRoute(builder: (context) => const SettingsScreen())).then((value) => topNav = ""); + widget.navigator + .push(CupertinoPageRoute( + builder: (context) => const SettingsScreen())) + .then((value) => topNav = ""); topNav = "settings"; } }, @@ -159,7 +168,7 @@ class _SidebarState extends State { onPressed: () { Navigator.of(context).pushNamed("login_back"); }, - title: const Text("Add User"), + title: Text("adduser".i18n), leading: const Icon(FeatherIcons.userPlus), ), PanelButton( @@ -169,17 +178,20 @@ class _SidebarState extends State { // Delete User user.removeUser(userId); - await Provider.of(context, listen: false).store.removeUser(userId); + await Provider.of(context, listen: false) + .store + .removeUser(userId); // If no other Users left, go back to LoginScreen if (user.getUsers().isNotEmpty) { user.setUser(user.getUsers().first.id); restore().then((_) => user.setUser(user.getUsers().first.id)); } else { - Navigator.of(context).pushNamedAndRemoveUntil("login", (_) => false); + Navigator.of(context) + .pushNamedAndRemoveUntil("login", (_) => false); } }, - title: const Text("Log Out"), + title: Text("logout".i18n), leading: Icon(FeatherIcons.logOut, color: AppColors.of(context).red), ), ]; @@ -190,49 +202,77 @@ class _SidebarState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.only(left: 18.0, top: 18.0, bottom: 24.0, right: 8.0), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 12.0), - child: ProfileImage( - name: firstName, - radius: 18.0, - backgroundColor: - !settings.presentationMode ? ColorUtils.stringToColor(user.name ?? "?") : Theme.of(context).colorScheme.secondary, - ), - ), - Expanded( - child: Text( - firstName, - style: const TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w600, + padding: const EdgeInsets.only( + left: 12.0, + top: 18.0, + bottom: 24.0, + right: 12.0, + ), + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + onTap: () { + setState(() { + expandAccount = !expandAccount; + }); + }, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only( + right: 12.0, + left: 5.0, + top: 5.0, + bottom: 5.0, + ), + child: ProfileImage( + name: firstName, + radius: 18.0, + backgroundColor: Theme.of(context) + .colorScheme + .primary, //!settings.presentationMode + // ? ColorUtils.stringToColor(user.name ?? "?") + // : Theme.of(context).colorScheme.secondary, ), ), - ), - PageTransitionSwitcher( - transitionBuilder: (child, primaryAnimation, secondaryAnimation) { - return FadeThroughTransition( - fillColor: Colors.transparent, - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - child: child, - ); - }, - child: IconButton( - key: Key(expandAccount ? "accounts" : "pages"), - icon: Icon(expandAccount ? FeatherIcons.chevronDown : FeatherIcons.chevronRight), - padding: EdgeInsets.zero, - splashRadius: 20.0, - onPressed: () { - setState(() { - expandAccount = !expandAccount; - }); - }, + Expanded( + child: Text( + firstName, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + ), + ), ), - ), - ], + PageTransitionSwitcher( + transitionBuilder: + (child, primaryAnimation, secondaryAnimation) { + return FadeThroughTransition( + fillColor: Colors.transparent, + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + child: child, + ); + }, + child: IconButton( + key: Key(expandAccount ? "accounts" : "pages"), + icon: Icon(expandAccount + ? FeatherIcons.chevronDown + : FeatherIcons.chevronRight), + padding: EdgeInsets.zero, + onPressed: () { + setState(() { + expandAccount = !expandAccount; + }); + }, + splashColor: const Color(0x00000000), + focusColor: const Color(0x00000000), + hoverColor: const Color(0x00000000), + highlightColor: const Color(0x00000000), + ), + ), + ], + ), ), ), @@ -282,15 +322,19 @@ class _SidebarState extends State { if (!settings.presentationMode) { _firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0]; } else { - _firstName = "Béla"; + _firstName = "János"; } accountTiles.add(AccountTile( - name: Text(!settings.presentationMode ? account.name : "Béla", style: const TextStyle(fontWeight: FontWeight.w500)), - username: Text(!settings.presentationMode ? account.username : "72469696969"), + name: Text(!settings.presentationMode ? account.name : "János", + style: const TextStyle(fontWeight: FontWeight.w500)), + username: + Text(!settings.presentationMode ? account.username : "72469696969"), profileImage: ProfileImage( name: _firstName, - backgroundColor: !settings.presentationMode ? ColorUtils.stringToColor(account.name) : Theme.of(context).colorScheme.secondary, + backgroundColor: !settings.presentationMode + ? ColorUtils.stringToColor(account.name) + : Theme.of(context).colorScheme.secondary, role: account.role, ), onTap: () { diff --git a/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.i18n.dart b/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.i18n.dart new file mode 100644 index 0000000..43cfb8a --- /dev/null +++ b/filcnaplo_desktop_ui/lib/screens/navigation/sidebar.i18n.dart @@ -0,0 +1,42 @@ +import 'package:i18n_extension/i18n_extension.dart'; + +extension SettingsLocalization on String { + static final _t = Translations.byLocale("hu_hu") + + { + "en_en": { + "Home": "Home", + "Grades": "Grades", + "Timetable": "Timetable", + "Messages": "Messages", + "Absences": "Absences", + "Settings": "Settings", + "adduser": "Add User", + "logout": "Log Out", + }, + "hu_hu": { + "Home": "Kezdőlap", + "Grades": "Jegyek", + "Timetable": "Órarend", + "Messages": "Üzenetek", + "Absences": "Hiányzások", + "Settings": "Beállítások", + "adduser": "Fiók hozzáadása", + "logout": "Kilépés", + }, + "de_de": { + "Home": "Zuhause", + "Grades": "Noten", + "Timetable": "Zeitplan", + "Messages": "Mitteilungen", + "Absences": "Fehlen", + "Settings": "Einstellungen", + "adduser": "Benutzer hinzufügen", + "logout": "Abmelden", + }, + }; + + String get i18n => localize(this, _t); + String fill(List params) => localizeFill(this, params); + String plural(int value) => localizePlural(value, this, _t); + String version(Object modifier) => localizeVersion(modifier, this, _t); +} diff --git a/filcnaplo_desktop_ui/lib/screens/settings/settings_screen.dart b/filcnaplo_desktop_ui/lib/screens/settings/settings_screen.dart index 83504f6..aef41ff 100644 --- a/filcnaplo_desktop_ui/lib/screens/settings/settings_screen.dart +++ b/filcnaplo_desktop_ui/lib/screens/settings/settings_screen.dart @@ -35,6 +35,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as tabs; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'settings_screen.i18n.dart'; @@ -86,11 +87,11 @@ class _SettingsScreenState extends State if (!settings.presentationMode) { _firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0]; } else { - _firstName = "Béla"; + _firstName = "János"; } accountTiles.add(AccountTile( - name: Text(!settings.presentationMode ? account.name : "Béla", + name: Text(!settings.presentationMode ? account.name : "János", style: const TextStyle(fontWeight: FontWeight.w500)), username: Text(!settings.presentationMode ? account.username : "72469696969"), @@ -164,7 +165,7 @@ class _SettingsScreenState extends State if (!settings.presentationMode) { firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0]; } else { - firstName = "Béla"; + firstName = "János"; } String startPageTitle = @@ -206,307 +207,403 @@ class _SettingsScreenState extends State animation: _hideContainersController, builder: (context, child) => Opacity( opacity: 1 - _hideContainersController.value, - child: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StaggeredGrid.extent( + // direction: Axis.horizontal, + // crossAxisCount: 3, + maxCrossAxisExtent: 600, + children: [ + const SizedBox(height: 32.0), - // Updates - if (updateProvider.available) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - child: PanelButton( - onPressed: () => _openUpdates(context), - title: Text("update_available".i18n), - leading: const Icon(FeatherIcons.download), - trailing: Text( - updateProvider.releases.first.tag, - style: TextStyle( - fontWeight: FontWeight.w500, - color: - Theme.of(context).colorScheme.secondary, - ), - ), - ), - ), - ), - - // const Padding( - // padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), - // child: PremiumBannerButton(), - // ), - if (!Provider.of(context).hasPremium) - const ClipRect( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 12.0), - child: PremiumButton(), - ), - ), - - // General Settings - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: Text("general".i18n), - child: Column( - children: [ - PanelButton( - onPressed: () { - SettingsHelper.language(context); - setState(() {}); - }, - title: Text("language".i18n), - leading: const Icon(FeatherIcons.globe), - trailing: Text(languageText), - ), - PanelButton( - onPressed: () { - SettingsHelper.startPage(context); - setState(() {}); - }, - title: Text("startpage".i18n), - leading: const Icon(FeatherIcons.play), - trailing: Text(startPageTitle.capital()), - ), - PanelButton( - onPressed: () { - SettingsHelper.rounding(context); - setState(() {}); - }, - title: Text("rounding".i18n), - leading: const Icon(FeatherIcons.gitCommit), - trailing: Text((settings.rounding / 10) - .toStringAsFixed(1)), - ), - PanelButton( - onPressed: () { - SettingsHelper.vibrate(context); - setState(() {}); - }, - title: Text("vibrate".i18n), - leading: const Icon(FeatherIcons.radio), - trailing: Text(vibrateTitle), - ), - PanelButton( - padding: const EdgeInsets.only(left: 14.0), - onPressed: () { - SettingsHelper.bellDelay(context); - setState(() {}); - }, - title: Text( - "bell_delay".i18n, - style: TextStyle( - color: AppColors.of(context) - .text - .withOpacity( - settings.bellDelayEnabled - ? 1.0 - : .5)), - ), - leading: settings.bellDelayEnabled - ? const Icon(FeatherIcons.bell) - : Icon(FeatherIcons.bellOff, - color: AppColors.of(context) - .text - .withOpacity(.25)), - trailingDivider: true, - trailing: Switch( - onChanged: (v) => - settings.update(bellDelayEnabled: v), - value: settings.bellDelayEnabled, - activeColor: - Theme.of(context).colorScheme.secondary, - ), - ), - ], - ), - ), - ), - - if (kDebugMode) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: const Text("Debug"), - child: Column( - children: [ - PanelButton( - title: const Text("Subject Icon Gallery"), - leading: const Icon(CupertinoIcons - .rectangle_3_offgrid_fill), - trailing: const Icon(Icons.arrow_forward), - onPressed: () { - Navigator.of(context, rootNavigator: true) - .push( - CupertinoPageRoute( - builder: (context) => - const SubjectIconGallery()), - ); - }, - ) - ], - ), - ), - ), - - // Secret Settings - if (__ss) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: Text("secret".i18n), - child: Column( - children: [ - // Good student mode - Material( - type: MaterialType.transparency, - child: SwitchListTile( - contentPadding: - const EdgeInsets.only(left: 12.0), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0)), - title: Text("goodstudent".i18n, - style: const TextStyle( - fontWeight: FontWeight.w500)), - onChanged: (v) { - if (v) { - showDialog( - context: context, - builder: (context) => WillPopScope( - onWillPop: () async => false, - child: AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 12.0)), - title: Text("attention".i18n), - content: Text( - "goodstudent_disclaimer" - .i18n), - actions: [ - ActionButton( - label: "understand".i18n, - onTap: () { - Navigator.of(context) - .pop(); - settings.update( - goodStudent: v); - Provider.of( - context, - listen: false) - .fetch(); - }) - ], - ), - ), - ); - } else { - settings.update(goodStudent: v); - Provider.of(context, - listen: false) - .fetch(); - } - }, - value: settings.goodStudent, - activeColor: Theme.of(context) - .colorScheme - .secondary, - ), - ), - - // Presentation mode - Material( - type: MaterialType.transparency, - child: SwitchListTile( - contentPadding: - const EdgeInsets.only(left: 12.0), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0)), - title: const Text("Presentation Mode", - style: TextStyle( - fontWeight: FontWeight.w500)), - onChanged: (v) => - settings.update(presentationMode: v), - value: settings.presentationMode, - activeColor: Theme.of(context) - .colorScheme - .secondary, - ), - ), - ], - ), - ), - ), - - // Theme Settings - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: Text("appearance".i18n), - child: Column( - children: [ - PanelButton( - onPressed: () { - SettingsHelper.theme(context); - setState(() {}); - }, - title: Text("theme".i18n), - leading: const Icon(FeatherIcons.sun), - trailing: Text(themeModeText), - ), - PanelButton( - onPressed: () async { - await _hideContainersController.forward(); - SettingsHelper.accentColor(context); - setState(() {}); - _hideContainersController.reset(); - }, - title: Text("color".i18n), - leading: const Icon(FeatherIcons.droplet), - trailing: Container( - width: 12.0, - height: 12.0, - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .secondary, - shape: BoxShape.circle, - ), - ), - ), - PanelButton( - onPressed: () { - SettingsHelper.gradeColors(context); - setState(() {}); - }, - title: Text("grade_colors".i18n), - leading: const Icon(FeatherIcons.star), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: List.generate( - 5, - (i) => Container( - margin: - const EdgeInsets.only(left: 2.0), - width: 12.0, - height: 12.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: settings.gradeColors[i], - ), + // Updates + if (updateProvider.available) + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + child: PanelButton( + onPressed: () => _openUpdates(context), + title: Text("update_available".i18n), + leading: const Icon(FeatherIcons.download), + trailing: Text( + updateProvider.releases.first.tag, + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context) + .colorScheme + .secondary, ), ), ), ), - Material( + ), + ), + + // const Padding( + // padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), + // child: PremiumBannerButton(), + // ), + if (!Provider.of(context).hasPremium) + const ClipRect( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12.0), + child: PremiumButton(), + ), + ), + + // General Settings + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: Text("general".i18n), + child: Column( + children: [ + PanelButton( + onPressed: () { + SettingsHelper.language(context); + setState(() {}); + }, + title: Text("language".i18n), + leading: const Icon(FeatherIcons.globe), + trailing: Text(languageText), + ), + PanelButton( + onPressed: () { + SettingsHelper.startPage(context); + setState(() {}); + }, + title: Text("startpage".i18n), + leading: const Icon(FeatherIcons.play), + trailing: Text(startPageTitle.capital()), + ), + PanelButton( + onPressed: () { + SettingsHelper.rounding(context); + setState(() {}); + }, + title: Text("rounding".i18n), + leading: + const Icon(FeatherIcons.gitCommit), + trailing: Text((settings.rounding / 10) + .toStringAsFixed(1)), + ), + PanelButton( + onPressed: () { + SettingsHelper.vibrate(context); + setState(() {}); + }, + title: Text("vibrate".i18n), + leading: const Icon(FeatherIcons.radio), + trailing: Text(vibrateTitle), + ), + PanelButton( + padding: + const EdgeInsets.only(left: 14.0), + onPressed: () { + SettingsHelper.bellDelay(context); + setState(() {}); + }, + title: Text( + "bell_delay".i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity( + settings.bellDelayEnabled + ? 1.0 + : .5)), + ), + leading: settings.bellDelayEnabled + ? const Icon(FeatherIcons.bell) + : Icon(FeatherIcons.bellOff, + color: AppColors.of(context) + .text + .withOpacity(.25)), + trailingDivider: true, + trailing: Switch( + onChanged: (v) => settings.update( + bellDelayEnabled: v), + value: settings.bellDelayEnabled, + activeColor: Theme.of(context) + .colorScheme + .secondary, + ), + ), + ], + ), + ), + ), + ), + + if (kDebugMode) + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: const Text("Debug"), + child: Column( + children: [ + PanelButton( + title: + const Text("Subject Icon Gallery"), + leading: const Icon(CupertinoIcons + .rectangle_3_offgrid_fill), + trailing: + const Icon(Icons.arrow_forward), + onPressed: () { + Navigator.of(context, + rootNavigator: true) + .push( + CupertinoPageRoute( + builder: (context) => + const SubjectIconGallery()), + ); + }, + ) + ], + ), + ), + ), + ), + + // Secret Settings + if (__ss) + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: Text("secret".i18n), + child: Column( + children: [ + // Good student mode + Material( + type: MaterialType.transparency, + child: SwitchListTile( + contentPadding: + const EdgeInsets.only(left: 12.0), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0)), + title: Text("goodstudent".i18n, + style: const TextStyle( + fontWeight: FontWeight.w500)), + onChanged: (v) { + if (v) { + showDialog( + context: context, + builder: (context) => + WillPopScope( + onWillPop: () async => false, + child: AlertDialog( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular( + 12.0)), + title: + Text("attention".i18n), + content: Text( + "goodstudent_disclaimer" + .i18n), + actions: [ + ActionButton( + label: + "understand".i18n, + onTap: () { + Navigator.of( + context) + .pop(); + settings.update( + goodStudent: v); + Provider.of( + context, + listen: + false) + .fetch(); + }) + ], + ), + ), + ); + } else { + settings.update(goodStudent: v); + Provider.of( + context, + listen: false) + .fetch(); + } + }, + value: settings.goodStudent, + activeColor: Theme.of(context) + .colorScheme + .secondary, + ), + ), + + // Presentation mode + Material( + type: MaterialType.transparency, + child: SwitchListTile( + contentPadding: + const EdgeInsets.only(left: 12.0), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0)), + title: const Text("Presentation Mode", + style: TextStyle( + fontWeight: FontWeight.w500)), + onChanged: (v) => settings.update( + presentationMode: v), + value: settings.presentationMode, + activeColor: Theme.of(context) + .colorScheme + .secondary, + ), + ), + ], + ), + ), + ), + ), + + // Theme Settings + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: Text("appearance".i18n), + child: Column( + children: [ + PanelButton( + onPressed: () { + SettingsHelper.theme(context); + setState(() {}); + }, + title: Text("theme".i18n), + leading: const Icon(FeatherIcons.sun), + trailing: Text(themeModeText), + ), + PanelButton( + onPressed: () async { + await _hideContainersController + .forward(); + SettingsHelper.accentColor(context); + setState(() {}); + _hideContainersController.reset(); + }, + title: Text("color".i18n), + leading: const Icon(FeatherIcons.droplet), + trailing: Container( + width: 12.0, + height: 12.0, + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .secondary, + shape: BoxShape.circle, + ), + ), + ), + PanelButton( + onPressed: () { + SettingsHelper.gradeColors(context); + setState(() {}); + }, + title: Text("grade_colors".i18n), + leading: const Icon(FeatherIcons.star), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + 5, + (i) => Container( + margin: const EdgeInsets.only( + left: 2.0), + width: 12.0, + height: 12.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: settings.gradeColors[i], + ), + ), + ), + ), + ), + Material( + type: MaterialType.transparency, + child: SwitchListTile( + contentPadding: + const EdgeInsets.only(left: 12.0), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0)), + title: Row( + children: [ + Icon( + FeatherIcons.barChart, + color: settings.graphClassAvg + ? Theme.of(context) + .colorScheme + .secondary + : AppColors.of(context) + .text + .withOpacity(.25), + ), + const SizedBox(width: 24.0), + Expanded( + child: Text( + "graph_class_avg".i18n, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.0, + color: AppColors.of(context) + .text + .withOpacity( + settings.graphClassAvg + ? 1.0 + : .5), + ), + ), + ), + ], + ), + onChanged: (v) => + settings.update(graphClassAvg: v), + value: settings.graphClassAvg, + activeColor: Theme.of(context) + .colorScheme + .secondary, + ), + ), + const PremiumIconPackSelector(), + ], + ), + ), + ), + ), + + // Notifications + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: Text("notifications".i18n), + child: Material( type: MaterialType.transparency, child: SwitchListTile( contentPadding: @@ -517,8 +614,8 @@ class _SettingsScreenState extends State title: Row( children: [ Icon( - FeatherIcons.barChart, - color: settings.graphClassAvg + Icons.newspaper_outlined, + color: settings.newsEnabled ? Theme.of(context) .colorScheme .secondary @@ -529,14 +626,14 @@ class _SettingsScreenState extends State const SizedBox(width: 24.0), Expanded( child: Text( - "graph_class_avg".i18n, + "news".i18n, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16.0, color: AppColors.of(context) .text .withOpacity( - settings.graphClassAvg + settings.newsEnabled ? 1.0 : .5), ), @@ -545,252 +642,25 @@ class _SettingsScreenState extends State ], ), onChanged: (v) => - settings.update(graphClassAvg: v), - value: settings.graphClassAvg, - activeColor: - Theme.of(context).colorScheme.secondary, - ), - ), - const PremiumIconPackSelector(), - ], - ), - ), - ), - - // Notifications - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: Text("notifications".i18n), - child: Material( - type: MaterialType.transparency, - child: SwitchListTile( - contentPadding: - const EdgeInsets.only(left: 12.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0)), - title: Row( - children: [ - Icon( - Icons.newspaper_outlined, - color: settings.newsEnabled - ? Theme.of(context) - .colorScheme - .secondary - : AppColors.of(context) - .text - .withOpacity(.25), - ), - const SizedBox(width: 24.0), - Expanded( - child: Text( - "news".i18n, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16.0, - color: AppColors.of(context) - .text - .withOpacity(settings.newsEnabled - ? 1.0 - : .5), - ), - ), - ), - ], - ), - onChanged: (v) => - settings.update(newsEnabled: v), - value: settings.newsEnabled, - activeColor: - Theme.of(context).colorScheme.secondary, - ), - ), - ), - ), - - // Extras - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: Text("extras".i18n), - child: Column(children: [ - Material( - type: MaterialType.transparency, - child: SwitchListTile( - contentPadding: - const EdgeInsets.only(left: 12.0), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0)), - title: Row( - children: [ - Icon( - FeatherIcons.gift, - color: settings.gradeOpeningFun - ? Theme.of(context) - .colorScheme - .secondary - : AppColors.of(context) - .text - .withOpacity(.25), - ), - const SizedBox(width: 24.0), - Expanded( - child: Text( - "surprise_grades".i18n, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16.0, - color: AppColors.of(context) - .text - .withOpacity( - settings.gradeOpeningFun - ? 1.0 - : .5), - ), - ), - ), - ], - ), - onChanged: (v) => - settings.update(gradeOpeningFun: v), - value: settings.gradeOpeningFun, - activeColor: - Theme.of(context).colorScheme.secondary, - ), - ), - ]), - ), - ), - - // About - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: Text("about".i18n), - child: Column(children: [ - PanelButton( - leading: const Icon(FeatherIcons.atSign), - title: const Text("Discord"), - onPressed: () => launchUrl( - Uri.parse("https://filcnaplo.hu/discord"), - mode: LaunchMode.externalApplication), - ), - PanelButton( - leading: const Icon(FeatherIcons.globe), - title: const Text("www.filcnaplo.hu"), - onPressed: () => launchUrl( - Uri.parse("https://filcnaplo.hu"), - mode: LaunchMode.externalApplication), - ), - PanelButton( - leading: const Icon(FeatherIcons.github), - title: const Text("Github"), - onPressed: () => launchUrl( - Uri.parse("https://github.com/filc"), - mode: LaunchMode.externalApplication), - ), - PanelButton( - leading: const Icon(FeatherIcons.mail), - title: Text("news".i18n), - onPressed: () => _openNews(context), - ), - PanelButton( - leading: const Icon(FeatherIcons.lock), - title: Text("privacy".i18n), - onPressed: () => _openPrivacy(context), - ), - PanelButton( - leading: const Icon(FeatherIcons.award), - title: Text("licenses".i18n), - onPressed: () => - showLicensePage(context: context), - ), - Tooltip( - message: "data_collected".i18n, - padding: const EdgeInsets.all(4.0), - textStyle: TextStyle( - fontWeight: FontWeight.w500, - color: AppColors.of(context).text), - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .background), - child: Material( - type: MaterialType.transparency, - child: SwitchListTile( - contentPadding: - const EdgeInsets.only(left: 12.0), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0)), - secondary: Icon( - FeatherIcons.barChart2, - color: settings.xFilcId != "none" - ? Theme.of(context) - .colorScheme - .secondary - : AppColors.of(context) - .text - .withOpacity(.25), - ), - title: Text( - "Analytics".i18n, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16.0, - color: AppColors.of(context) - .text - .withOpacity( - settings.xFilcId != "none" - ? 1.0 - : .5), - ), - ), - subtitle: Text( - "Anonymous Usage Analytics".i18n, - style: TextStyle( - color: AppColors.of(context) - .text - .withOpacity( - settings.xFilcId != "none" - ? .5 - : .2), - ), - ), - onChanged: (v) { - String newId; - if (v == false) { - newId = "none"; - } else if (settings.xFilcId == "none") { - newId = - SettingsProvider.defaultSettings() - .xFilcId; - } else { - newId = settings.xFilcId; - } - settings.update(xFilcId: newId); - }, - value: settings.xFilcId != "none", + settings.update(newsEnabled: v), + value: settings.newsEnabled, activeColor: Theme.of(context).colorScheme.secondary, ), ), ), - ]), + ), ), - ), - if (settings.developerMode) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, horizontal: 24.0), - child: Panel( - title: const Text("Developer Settings"), - child: Column( - children: [ + + // Extras + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: Text("extras".i18n), + child: Column(children: [ Material( type: MaterialType.transparency, child: SwitchListTile( @@ -799,88 +669,280 @@ class _SettingsScreenState extends State shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0)), - title: const Text("Developer Mode", - style: TextStyle( - fontWeight: FontWeight.w500)), + title: Row( + children: [ + Icon( + FeatherIcons.gift, + color: settings.gradeOpeningFun + ? Theme.of(context) + .colorScheme + .secondary + : AppColors.of(context) + .text + .withOpacity(.25), + ), + const SizedBox(width: 24.0), + Expanded( + child: Text( + "surprise_grades".i18n, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.0, + color: AppColors.of(context) + .text + .withOpacity( + settings.gradeOpeningFun + ? 1.0 + : .5), + ), + ), + ), + ], + ), onChanged: (v) => - settings.update(developerMode: false), - value: settings.developerMode, + settings.update(gradeOpeningFun: v), + value: settings.gradeOpeningFun, activeColor: Theme.of(context) .colorScheme .secondary, ), ), - PanelButton( - leading: const Icon(FeatherIcons.copy), - title: const Text("Copy JWT"), - onPressed: () => Clipboard.setData( - ClipboardData( - text: Provider.of( - context, - listen: false) - .accessToken!)), - ), - if (Provider.of(context, - listen: false) - .hasPremium) - PanelButton( - leading: const Icon(FeatherIcons.key), - title: const Text("Remove Premium"), - onPressed: () { - Provider.of(context, - listen: false) - .activate(removePremium: true); - settings.update( - accentColor: AccentColor.filc, - store: true); - Provider.of(context, - listen: false) - .changeTheme(settings.theme); - }, - ), - ], + ]), ), ), ), - SafeArea( - top: false, - child: Center( - child: GestureDetector( - child: const Panel( - title: Text("v" + - String.fromEnvironment("APPVER", - defaultValue: "?"))), - onTap: () { - if (devmodeCountdown > 0) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar( - duration: const Duration(milliseconds: 200), - content: Text( - "You are $devmodeCountdown taps away from Developer Mode."), - )); - setState(() => devmodeCountdown--); - } else if (devmodeCountdown == 0) { - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar( - content: Text( - "Developer Mode successfully activated."), - )); - - settings.update(developerMode: true); - - setState(() => devmodeCountdown--); - } - }, + // About + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: Text("about".i18n), + child: Column(children: [ + PanelButton( + leading: const Icon(FeatherIcons.atSign), + title: const Text("Discord"), + onPressed: () => launchUrl( + Uri.parse( + "https://filcnaplo.hu/discord"), + mode: LaunchMode.externalApplication), + ), + PanelButton( + leading: const Icon(FeatherIcons.globe), + title: const Text("www.filcnaplo.hu"), + onPressed: () => launchUrl( + Uri.parse("https://filcnaplo.hu"), + mode: LaunchMode.externalApplication), + ), + PanelButton( + leading: const Icon(FeatherIcons.github), + title: const Text("Github"), + onPressed: () => launchUrl( + Uri.parse("https://github.com/filc"), + mode: LaunchMode.externalApplication), + ), + PanelButton( + leading: const Icon(FeatherIcons.mail), + title: Text("news".i18n), + onPressed: () => _openNews(context), + ), + PanelButton( + leading: const Icon(FeatherIcons.lock), + title: Text("privacy".i18n), + onPressed: () => _openPrivacy(context), + ), + PanelButton( + leading: const Icon(FeatherIcons.award), + title: Text("licenses".i18n), + onPressed: () => + showLicensePage(context: context), + ), + Tooltip( + message: "data_collected".i18n, + padding: const EdgeInsets.all(4.0), + textStyle: TextStyle( + fontWeight: FontWeight.w500, + color: AppColors.of(context).text), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .background), + child: Material( + type: MaterialType.transparency, + child: SwitchListTile( + contentPadding: + const EdgeInsets.only(left: 12.0), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0)), + secondary: Icon( + FeatherIcons.barChart2, + color: settings.xFilcId != "none" + ? Theme.of(context) + .colorScheme + .secondary + : AppColors.of(context) + .text + .withOpacity(.25), + ), + title: Text( + "Analytics".i18n, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.0, + color: AppColors.of(context) + .text + .withOpacity( + settings.xFilcId != "none" + ? 1.0 + : .5), + ), + ), + subtitle: Text( + "Anonymous Usage Analytics".i18n, + style: TextStyle( + color: AppColors.of(context) + .text + .withOpacity( + settings.xFilcId != "none" + ? .5 + : .2), + ), + ), + onChanged: (v) { + String newId; + if (v == false) { + newId = "none"; + } else if (settings.xFilcId == + "none") { + newId = SettingsProvider + .defaultSettings() + .xFilcId; + } else { + newId = settings.xFilcId; + } + settings.update(xFilcId: newId); + }, + value: settings.xFilcId != "none", + activeColor: Theme.of(context) + .colorScheme + .secondary, + ), + ), + ), + ]), + ), ), ), + if (settings.developerMode) + Container( + constraints: const BoxConstraints(maxWidth: 500), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, horizontal: 24.0), + child: Panel( + title: const Text("Developer Settings"), + child: Column( + children: [ + Material( + type: MaterialType.transparency, + child: SwitchListTile( + contentPadding: + const EdgeInsets.only(left: 12.0), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0)), + title: const Text("Developer Mode", + style: TextStyle( + fontWeight: FontWeight.w500)), + onChanged: (v) => settings.update( + developerMode: false), + value: settings.developerMode, + activeColor: Theme.of(context) + .colorScheme + .secondary, + ), + ), + PanelButton( + leading: const Icon(FeatherIcons.copy), + title: const Text("Copy JWT"), + onPressed: () => Clipboard.setData( + ClipboardData( + text: Provider.of( + context, + listen: false) + .accessToken!)), + ), + // if (Provider.of(context, + // listen: false) + // .hasPremium) + // PanelButton( + // leading: const Icon(FeatherIcons.key), + // title: const Text("Remove Premium"), + // onPressed: () { + // Provider.of( + // context, + // listen: false) + // .activate(removePremium: true); + // settings.update( + // accentColor: AccentColor.filc, + // store: true); + // Provider.of( + // context, + // listen: false) + // .changeTheme(settings.theme); + // }, + // ), + ], + ), + ), + ), + ), + ], + ), + const SizedBox( + height: 40, + ), + SafeArea( + top: false, + child: Center( + child: GestureDetector( + child: const Panel( + title: Text("v" + + String.fromEnvironment("APPVER", + defaultValue: "?"))), + onTap: () { + if (devmodeCountdown > 0) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: const Duration(milliseconds: 200), + content: Text( + "You are $devmodeCountdown taps away from Developer Mode."), + )); + + setState(() => devmodeCountdown--); + } else if (devmodeCountdown == 0) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text( + "Developer Mode successfully activated."), + )); + + settings.update(developerMode: true); + + setState(() => devmodeCountdown--); + } + }, + ), ), - ], - ), + ), + ], ), ), ), - ) + ), ], ), ), diff --git a/filcnaplo_desktop_ui/pubspec.yaml b/filcnaplo_desktop_ui/pubspec.yaml index 676f882..2a1645a 100644 --- a/filcnaplo_desktop_ui/pubspec.yaml +++ b/filcnaplo_desktop_ui/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: auto_size_text: ^3.0.0 flutter_acrylic: ^1.1.3 elegant_notification: ^1.6.1 + flutter_staggered_grid_view: ^0.7.0 dev_dependencies: flutter_lints: ^1.0.0 diff --git a/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart b/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart index 9207f72..8ba3974 100755 --- a/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart +++ b/filcnaplo_mobile_ui/lib/common/widgets/absence_group/absence_group_tile.dart @@ -8,7 +8,9 @@ import 'package:flutter/material.dart'; import 'absence_group_tile.i18n.dart'; class AbsenceGroupTile extends StatelessWidget { - const AbsenceGroupTile(this.absences, {Key? key, this.showDate = false, this.padding}) : super(key: key); + const AbsenceGroupTile(this.absences, + {Key? key, this.showDate = false, this.padding}) + : super(key: key); final List absences; final bool showDate; @@ -16,10 +18,12 @@ class AbsenceGroupTile extends StatelessWidget { @override Widget build(BuildContext context) { - Justification state = getState(absences.map((e) => e.absence.state).toList()); + Justification state = + getState(absences.map((e) => e.absence.state).toList()); Color color = AbsenceTile.justificationColor(state, context: context); - absences.sort((a, b) => a.absence.lessonIndex?.compareTo(b.absence.lessonIndex ?? 0) ?? -1); + absences.sort((a, b) => + a.absence.lessonIndex?.compareTo(b.absence.lessonIndex ?? 0) ?? -1); return ClipRRect( borderRadius: BorderRadius.circular(14.0), @@ -29,6 +33,8 @@ class AbsenceGroupTile extends StatelessWidget { padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0), child: AbsenceGroupContainer( child: ExpansionTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), tilePadding: const EdgeInsets.symmetric(horizontal: 8.0), backgroundColor: Colors.transparent, leading: Container( @@ -38,22 +44,33 @@ class AbsenceGroupTile extends StatelessWidget { shape: BoxShape.circle, color: color.withOpacity(.25), ), - child: Center(child: Icon(AbsenceTile.justificationIcon(state), color: color)), + child: Center( + child: Icon(AbsenceTile.justificationIcon(state), + color: color)), ), title: Text.rich(TextSpan( - text: "${absences.where((a) => a.absence.state == state).length} ", - style: TextStyle(fontWeight: FontWeight.w700, color: AppColors.of(context).text), + text: + "${absences.where((a) => a.absence.state == state).length} ", + style: TextStyle( + fontWeight: FontWeight.w700, + color: AppColors.of(context).text), children: [ TextSpan( - text: AbsenceTile.justificationName(state).fill(["absence".i18n]), - style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.of(context).text), + text: AbsenceTile.justificationName(state) + .fill(["absence".i18n]), + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppColors.of(context).text), ), ], )), subtitle: showDate ? Text( - absences.first.absence.date.format(context, weekday: true), - style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.8)), + absences.first.absence.date + .format(context, weekday: true), + style: TextStyle( + fontWeight: FontWeight.w500, + color: AppColors.of(context).text.withOpacity(0.8)), ) : null, children: absences, From 93fab8196d4587736f4ab78f219c5f0d6601e6d9 Mon Sep 17 00:00:00 2001 From: Kima Date: Tue, 1 Aug 2023 18:47:10 +0200 Subject: [PATCH 5/7] added web support --- filcnaplo/.metadata | 5 +- filcnaplo/web/favicon.png | Bin 0 -> 917 bytes filcnaplo/web/icons/Icon-192.png | Bin 0 -> 5292 bytes filcnaplo/web/icons/Icon-512.png | Bin 0 -> 8252 bytes filcnaplo/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes filcnaplo/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes filcnaplo/web/index.html | 59 ++++++++++++++++++++++ filcnaplo/web/manifest.json | 35 +++++++++++++ 8 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 filcnaplo/web/favicon.png create mode 100644 filcnaplo/web/icons/Icon-192.png create mode 100644 filcnaplo/web/icons/Icon-512.png create mode 100644 filcnaplo/web/icons/Icon-maskable-192.png create mode 100644 filcnaplo/web/icons/Icon-maskable-512.png create mode 100644 filcnaplo/web/index.html create mode 100644 filcnaplo/web/manifest.json diff --git a/filcnaplo/.metadata b/filcnaplo/.metadata index 6b7daaf..bd1207f 100644 --- a/filcnaplo/.metadata +++ b/filcnaplo/.metadata @@ -15,10 +15,7 @@ migration: - platform: root create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 - - platform: linux - create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 - base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 - - platform: windows + - platform: web create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 diff --git a/filcnaplo/web/favicon.png b/filcnaplo/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/filcnaplo/web/icons/Icon-192.png b/filcnaplo/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/filcnaplo/web/icons/Icon-512.png b/filcnaplo/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/filcnaplo/web/icons/Icon-maskable-192.png b/filcnaplo/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/filcnaplo/web/icons/Icon-maskable-512.png b/filcnaplo/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/filcnaplo/web/index.html b/filcnaplo/web/index.html new file mode 100644 index 0000000..d806e3c --- /dev/null +++ b/filcnaplo/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + filcnaplo + + + + + + + + + + diff --git a/filcnaplo/web/manifest.json b/filcnaplo/web/manifest.json new file mode 100644 index 0000000..d43c553 --- /dev/null +++ b/filcnaplo/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "filcnaplo", + "short_name": "filcnaplo", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} From 1f62a71cd5cc5c1fa5b8e7e24185762a9f49ec18 Mon Sep 17 00:00:00 2001 From: Kima Date: Tue, 1 Aug 2023 21:13:40 +0200 Subject: [PATCH 6/7] i'll never make this shit work on web lol, go nuxt --- filcnaplo/lib/app.dart | 16 +++++++++-- filcnaplo/lib/database/init.dart | 6 +++- filcnaplo/lib/main.dart | 48 ++++++++++++++++++-------------- filcnaplo/pubspec.yaml | 1 + 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/filcnaplo/lib/app.dart b/filcnaplo/lib/app.dart index efc5414..581b4a0 100644 --- a/filcnaplo/lib/app.dart +++ b/filcnaplo/lib/app.dart @@ -13,6 +13,7 @@ import 'package:filcnaplo/theme/theme.dart'; import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:i18n_extension/i18n_widget.dart'; @@ -194,8 +195,19 @@ class App extends StatelessWidget { } Route? rootNavigator(RouteSettings route) { - // if platform == android || platform == ios - if (Platform.isAndroid || Platform.isIOS) { + if (kIsWeb) { + switch (route.name) { + case "login_back": + return CupertinoPageRoute( + builder: (context) => const desktop.LoginScreen(back: true)); + case "login": + return _rootRoute(const desktop.LoginScreen()); + case "navigation": + return _rootRoute(const desktop.NavigationScreen()); + case "login_to_navigation": + return desktop.loginRoute(const desktop.NavigationScreen()); + } + } else if (Platform.isAndroid || Platform.isIOS) { switch (route.name) { case "login_back": return CupertinoPageRoute( diff --git a/filcnaplo/lib/database/init.dart b/filcnaplo/lib/database/init.dart index a31c6a6..bce6d24 100644 --- a/filcnaplo/lib/database/init.dart +++ b/filcnaplo/lib/database/init.dart @@ -5,8 +5,10 @@ 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:flutter/foundation.dart'; // ignore: depend_on_referenced_packages import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; const settingsDB = DatabaseStruct("settings", { "language": String, "start_page": int, "rounding": int, "theme": int, @@ -48,7 +50,9 @@ Future createTable(Database db, DatabaseStruct struct) => Future initDB(DatabaseProvider database) async { Database db; - if (Platform.isLinux || Platform.isWindows) { + if (kIsWeb) { + db = await databaseFactoryFfiWeb.openDatabase("app.db"); + } else if (Platform.isLinux || Platform.isWindows) { sqfliteFfiInit(); db = await databaseFactoryFfi.openDatabase("app.db"); } else { diff --git a/filcnaplo/lib/main.dart b/filcnaplo/lib/main.dart index a7ad317..b33cc5e 100644 --- a/filcnaplo/lib/main.dart +++ b/filcnaplo/lib/main.dart @@ -48,13 +48,17 @@ class Startup { settings = await database.query.getSettings(database); user = await database.query.getUsers(settings); + late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; // Notifications setup - initPlatformState(); - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); + if (!kIsWeb) { + initPlatformState(); + flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + } // Get permission to show notifications - if (Platform.isAndroid) { + if (kIsWeb) { + // do nothing + } else if (Platform.isAndroid) { await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>()! @@ -80,24 +84,26 @@ class Startup { } // Platform specific settings - const DarwinInitializationSettings initializationSettingsDarwin = - DarwinInitializationSettings( - requestSoundPermission: true, - requestBadgePermission: true, - requestAlertPermission: false, - ); - const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('ic_notification'); - const InitializationSettings initializationSettings = - InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsDarwin, - macOS: initializationSettingsDarwin); + if (!kIsWeb) { + const DarwinInitializationSettings initializationSettingsDarwin = + DarwinInitializationSettings( + requestSoundPermission: true, + requestBadgePermission: true, + requestAlertPermission: false, + ); + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('ic_notification'); + const InitializationSettings initializationSettings = + InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin, + macOS: initializationSettingsDarwin); - // Initialize notifications - await flutterLocalNotificationsPlugin.initialize( - initializationSettings, - ); + // Initialize notifications + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + ); + } } } diff --git a/filcnaplo/pubspec.yaml b/filcnaplo/pubspec.yaml index 5cd1e2d..ef1dffd 100644 --- a/filcnaplo/pubspec.yaml +++ b/filcnaplo/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: package_info_plus: ^4.0.2 screenshot: ^2.1.0 flutter_staggered_grid_view: ^0.7.0 + sqflite_common_ffi_web: ^0.4.0 dev_dependencies: flutter_lints: ^2.0.1 From 22cd08165bc126aedf414ff2767210c76fe39929 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 4 Aug 2023 13:48:36 +0200 Subject: [PATCH 7/7] added notification support on linux --- filcnaplo/lib/main.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/filcnaplo/lib/main.dart b/filcnaplo/lib/main.dart index b33cc5e..9ceab63 100644 --- a/filcnaplo/lib/main.dart +++ b/filcnaplo/lib/main.dart @@ -30,9 +30,10 @@ void main() async { // Run App runApp(App( - database: startup.database, - settings: startup.settings, - user: startup.user)); + database: startup.database, + settings: startup.settings, + user: startup.user, + )); } class Startup { @@ -81,6 +82,8 @@ class Startup { badge: true, sound: true, ); + } else if (Platform.isLinux) { + // no permissions are needed on linux } // Platform specific settings @@ -93,11 +96,15 @@ class Startup { ); const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('ic_notification'); + const LinuxInitializationSettings initializationSettingsLinux = + LinuxInitializationSettings(defaultActionName: 'Open notification'); const InitializationSettings initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsDarwin, - macOS: initializationSettingsDarwin); + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin, + macOS: initializationSettingsDarwin, + linux: initializationSettingsLinux, + ); // Initialize notifications await flutterLocalNotificationsPlugin.initialize(