forked from firka/student-legacy
Merge branch 'master' of github.com:refilc/naplo
This commit is contained in:
commit
40ca5fe0a1
@ -4,8 +4,8 @@
|
|||||||
# This file should be version controlled.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
channel: beta
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
@ -13,11 +13,11 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
- platform: macos
|
- platform: web
|
||||||
create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
|
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
@ -13,6 +13,7 @@ import 'package:filcnaplo/theme/theme.dart';
|
|||||||
import 'package:filcnaplo_kreta_api/client/client.dart';
|
import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:i18n_extension/i18n_widget.dart';
|
import 'package:i18n_extension/i18n_widget.dart';
|
||||||
@ -194,8 +195,19 @@ class App extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Route? rootNavigator(RouteSettings route) {
|
Route? rootNavigator(RouteSettings route) {
|
||||||
// if platform == android || platform == ios
|
if (kIsWeb) {
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
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) {
|
switch (route.name) {
|
||||||
case "login_back":
|
case "login_back":
|
||||||
return CupertinoPageRoute(
|
return CupertinoPageRoute(
|
||||||
|
@ -5,8 +5,10 @@ import 'dart:io';
|
|||||||
import 'package:filcnaplo/api/providers/database_provider.dart';
|
import 'package:filcnaplo/api/providers/database_provider.dart';
|
||||||
import 'package:filcnaplo/database/struct.dart';
|
import 'package:filcnaplo/database/struct.dart';
|
||||||
import 'package:filcnaplo/models/settings.dart';
|
import 'package:filcnaplo/models/settings.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
// ignore: depend_on_referenced_packages
|
// ignore: depend_on_referenced_packages
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
|
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
|
||||||
|
|
||||||
const settingsDB = DatabaseStruct("settings", {
|
const settingsDB = DatabaseStruct("settings", {
|
||||||
"language": String, "start_page": int, "rounding": int, "theme": int,
|
"language": String, "start_page": int, "rounding": int, "theme": int,
|
||||||
@ -48,7 +50,9 @@ Future<void> createTable(Database db, DatabaseStruct struct) =>
|
|||||||
Future<Database> initDB(DatabaseProvider database) async {
|
Future<Database> initDB(DatabaseProvider database) async {
|
||||||
Database db;
|
Database db;
|
||||||
|
|
||||||
if (Platform.isLinux || Platform.isWindows) {
|
if (kIsWeb) {
|
||||||
|
db = await databaseFactoryFfiWeb.openDatabase("app.db");
|
||||||
|
} else if (Platform.isLinux || Platform.isWindows) {
|
||||||
sqfliteFfiInit();
|
sqfliteFfiInit();
|
||||||
db = await databaseFactoryFfi.openDatabase("app.db");
|
db = await databaseFactoryFfi.openDatabase("app.db");
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,6 +6,7 @@ import 'package:filcnaplo/api/client.dart';
|
|||||||
import 'package:filcnaplo/helpers/storage_helper.dart';
|
import 'package:filcnaplo/helpers/storage_helper.dart';
|
||||||
import 'package:filcnaplo/models/release.dart';
|
import 'package:filcnaplo/models/release.dart';
|
||||||
import 'package:open_file/open_file.dart';
|
import 'package:open_file/open_file.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
enum UpdateState { none, preparing, downloading, installing }
|
enum UpdateState { none, preparing, downloading, installing }
|
||||||
|
|
||||||
@ -32,12 +33,17 @@ extension UpdateHelper on Release {
|
|||||||
|
|
||||||
updateCallback(-1, UpdateState.installing);
|
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) {
|
if (result.type != ResultType.done) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("ERROR: installUpdate.openFile: ${result.message}");
|
print("ERROR: installUpdate.openFile: ${result.message}");
|
||||||
throw result.message;
|
throw result.message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCallback(-1, UpdateState.none);
|
updateCallback(-1, UpdateState.none);
|
||||||
|
@ -30,9 +30,10 @@ void main() async {
|
|||||||
|
|
||||||
// Run App
|
// Run App
|
||||||
runApp(App(
|
runApp(App(
|
||||||
database: startup.database,
|
database: startup.database,
|
||||||
settings: startup.settings,
|
settings: startup.settings,
|
||||||
user: startup.user));
|
user: startup.user,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
class Startup {
|
class Startup {
|
||||||
@ -48,13 +49,17 @@ class Startup {
|
|||||||
settings = await database.query.getSettings(database);
|
settings = await database.query.getSettings(database);
|
||||||
user = await database.query.getUsers(settings);
|
user = await database.query.getUsers(settings);
|
||||||
|
|
||||||
|
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
||||||
// Notifications setup
|
// Notifications setup
|
||||||
initPlatformState();
|
if (!kIsWeb) {
|
||||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
initPlatformState();
|
||||||
FlutterLocalNotificationsPlugin();
|
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
// Get permission to show notifications
|
// Get permission to show notifications
|
||||||
if (Platform.isAndroid) {
|
if (kIsWeb) {
|
||||||
|
// do nothing
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
await flutterLocalNotificationsPlugin
|
await flutterLocalNotificationsPlugin
|
||||||
.resolvePlatformSpecificImplementation<
|
.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()!
|
AndroidFlutterLocalNotificationsPlugin>()!
|
||||||
@ -77,27 +82,35 @@ class Startup {
|
|||||||
badge: true,
|
badge: true,
|
||||||
sound: true,
|
sound: true,
|
||||||
);
|
);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
// no permissions are needed on linux
|
||||||
}
|
}
|
||||||
|
|
||||||
// Platform specific settings
|
// Platform specific settings
|
||||||
const DarwinInitializationSettings initializationSettingsDarwin =
|
if (!kIsWeb) {
|
||||||
DarwinInitializationSettings(
|
const DarwinInitializationSettings initializationSettingsDarwin =
|
||||||
requestSoundPermission: true,
|
DarwinInitializationSettings(
|
||||||
requestBadgePermission: true,
|
requestSoundPermission: true,
|
||||||
requestAlertPermission: false,
|
requestBadgePermission: true,
|
||||||
);
|
requestAlertPermission: false,
|
||||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
);
|
||||||
AndroidInitializationSettings('ic_notification');
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||||
const InitializationSettings initializationSettings =
|
AndroidInitializationSettings('ic_notification');
|
||||||
InitializationSettings(
|
const LinuxInitializationSettings initializationSettingsLinux =
|
||||||
android: initializationSettingsAndroid,
|
LinuxInitializationSettings(defaultActionName: 'Open notification');
|
||||||
iOS: initializationSettingsDarwin,
|
const InitializationSettings initializationSettings =
|
||||||
macOS: initializationSettingsDarwin);
|
InitializationSettings(
|
||||||
|
android: initializationSettingsAndroid,
|
||||||
|
iOS: initializationSettingsDarwin,
|
||||||
|
macOS: initializationSettingsDarwin,
|
||||||
|
linux: initializationSettingsLinux,
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize notifications
|
// Initialize notifications
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
initializationSettings,
|
initializationSettings,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez"
|
|||||||
homepage: https://refilc.hu
|
homepage: https://refilc.hu
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 4.1.1+215
|
version: 4.1.1+216
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
@ -66,6 +66,8 @@ dependencies:
|
|||||||
flutter_local_notifications: ^14.1.0
|
flutter_local_notifications: ^14.1.0
|
||||||
package_info_plus: ^4.0.2
|
package_info_plus: ^4.0.2
|
||||||
screenshot: ^2.1.0
|
screenshot: ^2.1.0
|
||||||
|
flutter_staggered_grid_view: ^0.7.0
|
||||||
|
sqflite_common_ffi_web: ^0.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
|
30
filcnaplo/test/widget_test.dart
Normal file
30
filcnaplo/test/widget_test.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// This is a basic Flutter widget test.
|
||||||
|
//
|
||||||
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
|
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||||
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:filcnaplo/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
// Build our app and trigger a frame.
|
||||||
|
await tester.pumpWidget(const MyApp());
|
||||||
|
|
||||||
|
// Verify that our counter starts at 0.
|
||||||
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
expect(find.text('1'), findsNothing);
|
||||||
|
|
||||||
|
// Tap the '+' icon and trigger a frame.
|
||||||
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify that our counter has incremented.
|
||||||
|
expect(find.text('0'), findsNothing);
|
||||||
|
expect(find.text('1'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
BIN
filcnaplo/web/favicon.png
Normal file
BIN
filcnaplo/web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
BIN
filcnaplo/web/icons/Icon-192.png
Normal file
BIN
filcnaplo/web/icons/Icon-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
filcnaplo/web/icons/Icon-512.png
Normal file
BIN
filcnaplo/web/icons/Icon-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
filcnaplo/web/icons/Icon-maskable-192.png
Normal file
BIN
filcnaplo/web/icons/Icon-maskable-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
BIN
filcnaplo/web/icons/Icon-maskable-512.png
Normal file
BIN
filcnaplo/web/icons/Icon-maskable-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
59
filcnaplo/web/index.html
Normal file
59
filcnaplo/web/index.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="filcnaplo">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>filcnaplo</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// The value below is injected by flutter build, do not touch.
|
||||||
|
var serviceWorkerVersion = null;
|
||||||
|
</script>
|
||||||
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
|
<script src="flutter.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(ev) {
|
||||||
|
// Download main.dart.js
|
||||||
|
_flutter.loader.loadEntrypoint({
|
||||||
|
serviceWorker: {
|
||||||
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
|
},
|
||||||
|
onEntrypointLoaded: function(engineInitializer) {
|
||||||
|
engineInitializer.initializeEngine().then(function(appRunner) {
|
||||||
|
appRunner.runApp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
filcnaplo/web/manifest.json
Normal file
35
filcnaplo/web/manifest.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "filcnaplo",
|
||||||
|
"short_name": "filcnaplo",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
17
filcnaplo/windows/.gitignore
vendored
Normal file
17
filcnaplo/windows/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
flutter/ephemeral/
|
||||||
|
|
||||||
|
# Visual Studio user-specific files.
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Visual Studio build-related files.
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
121
filcnaplo/windows/runner/Runner.rc
Normal file
121
filcnaplo/windows/runner/Runner.rc
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Microsoft Visual C++ generated resource script.
|
||||||
|
//
|
||||||
|
#pragma code_page(65001)
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
|
#define APSTUDIO_READONLY_SYMBOLS
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Generated from the TEXTINCLUDE 2 resource.
|
||||||
|
//
|
||||||
|
#include "winres.h"
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
#undef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// English (United States) resources
|
||||||
|
|
||||||
|
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||||
|
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
|
|
||||||
|
#ifdef APSTUDIO_INVOKED
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// TEXTINCLUDE
|
||||||
|
//
|
||||||
|
|
||||||
|
1 TEXTINCLUDE
|
||||||
|
BEGIN
|
||||||
|
"resource.h\0"
|
||||||
|
END
|
||||||
|
|
||||||
|
2 TEXTINCLUDE
|
||||||
|
BEGIN
|
||||||
|
"#include ""winres.h""\r\n"
|
||||||
|
"\0"
|
||||||
|
END
|
||||||
|
|
||||||
|
3 TEXTINCLUDE
|
||||||
|
BEGIN
|
||||||
|
"\r\n"
|
||||||
|
"\0"
|
||||||
|
END
|
||||||
|
|
||||||
|
#endif // APSTUDIO_INVOKED
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Icon
|
||||||
|
//
|
||||||
|
|
||||||
|
// Icon with lowest ID value placed first to ensure application icon
|
||||||
|
// remains consistent on all systems.
|
||||||
|
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Version
|
||||||
|
//
|
||||||
|
|
||||||
|
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
||||||
|
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
||||||
|
#else
|
||||||
|
#define VERSION_AS_NUMBER 1,0,0,0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(FLUTTER_VERSION)
|
||||||
|
#define VERSION_AS_STRING FLUTTER_VERSION
|
||||||
|
#else
|
||||||
|
#define VERSION_AS_STRING "1.0.0"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION VERSION_AS_NUMBER
|
||||||
|
PRODUCTVERSION VERSION_AS_NUMBER
|
||||||
|
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
|
#ifdef _DEBUG
|
||||||
|
FILEFLAGS VS_FF_DEBUG
|
||||||
|
#else
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
#endif
|
||||||
|
FILEOS VOS__WINDOWS32
|
||||||
|
FILETYPE VFT_APP
|
||||||
|
FILESUBTYPE 0x0L
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904e4"
|
||||||
|
BEGIN
|
||||||
|
VALUE "CompanyName", "hu.refilc" "\0"
|
||||||
|
VALUE "FileDescription", "filcnaplo" "\0"
|
||||||
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
|
VALUE "InternalName", "filcnaplo" "\0"
|
||||||
|
VALUE "LegalCopyright", "Copyright (C) 2023 hu.refilc. All rights reserved." "\0"
|
||||||
|
VALUE "OriginalFilename", "filcnaplo.exe" "\0"
|
||||||
|
VALUE "ProductName", "filcnaplo" "\0"
|
||||||
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x409, 1252
|
||||||
|
END
|
||||||
|
END
|
||||||
|
|
||||||
|
#endif // English (United States) resources
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef APSTUDIO_INVOKED
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Generated from the TEXTINCLUDE 3 resource.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
#endif // not APSTUDIO_INVOKED
|
66
filcnaplo/windows/runner/flutter_window.cpp
Normal file
66
filcnaplo/windows/runner/flutter_window.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "flutter_window.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||||
|
: project_(project) {}
|
||||||
|
|
||||||
|
FlutterWindow::~FlutterWindow() {}
|
||||||
|
|
||||||
|
bool FlutterWindow::OnCreate() {
|
||||||
|
if (!Win32Window::OnCreate()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT frame = GetClientArea();
|
||||||
|
|
||||||
|
// The size here must match the window dimensions to avoid unnecessary surface
|
||||||
|
// creation / destruction in the startup path.
|
||||||
|
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||||
|
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||||
|
// Ensure that basic setup of the controller was successful.
|
||||||
|
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RegisterPlugins(flutter_controller_->engine());
|
||||||
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||||
|
|
||||||
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||||
|
this->Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlutterWindow::OnDestroy() {
|
||||||
|
if (flutter_controller_) {
|
||||||
|
flutter_controller_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32Window::OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT
|
||||||
|
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||||
|
WPARAM const wparam,
|
||||||
|
LPARAM const lparam) noexcept {
|
||||||
|
// Give Flutter, including plugins, an opportunity to handle window messages.
|
||||||
|
if (flutter_controller_) {
|
||||||
|
std::optional<LRESULT> result =
|
||||||
|
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||||
|
lparam);
|
||||||
|
if (result) {
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
|
case WM_FONTCHANGE:
|
||||||
|
flutter_controller_->engine()->ReloadSystemFonts();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||||
|
}
|
33
filcnaplo/windows/runner/flutter_window.h
Normal file
33
filcnaplo/windows/runner/flutter_window.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
#define RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
|
||||||
|
#include <flutter/dart_project.h>
|
||||||
|
#include <flutter/flutter_view_controller.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "win32_window.h"
|
||||||
|
|
||||||
|
// A window that does nothing but host a Flutter view.
|
||||||
|
class FlutterWindow : public Win32Window {
|
||||||
|
public:
|
||||||
|
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
||||||
|
explicit FlutterWindow(const flutter::DartProject& project);
|
||||||
|
virtual ~FlutterWindow();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Win32Window:
|
||||||
|
bool OnCreate() override;
|
||||||
|
void OnDestroy() override;
|
||||||
|
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||||
|
LPARAM const lparam) noexcept override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The project to run.
|
||||||
|
flutter::DartProject project_;
|
||||||
|
|
||||||
|
// The Flutter instance hosted by this window.
|
||||||
|
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RUNNER_FLUTTER_WINDOW_H_
|
43
filcnaplo/windows/runner/main.cpp
Normal file
43
filcnaplo/windows/runner/main.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include <flutter/dart_project.h>
|
||||||
|
#include <flutter/flutter_view_controller.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "flutter_window.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
|
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||||
|
// Attach to console when present (e.g., 'flutter run') or create a
|
||||||
|
// new console when running with a debugger.
|
||||||
|
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||||
|
CreateAndAttachConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize COM, so that it is available for use in the library and/or
|
||||||
|
// plugins.
|
||||||
|
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
|
||||||
|
flutter::DartProject project(L"data");
|
||||||
|
|
||||||
|
std::vector<std::string> command_line_arguments =
|
||||||
|
GetCommandLineArguments();
|
||||||
|
|
||||||
|
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||||
|
|
||||||
|
FlutterWindow window(project);
|
||||||
|
Win32Window::Point origin(10, 10);
|
||||||
|
Win32Window::Size size(800, 720);
|
||||||
|
if (!window.Create(L"reFilc", origin, size)) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
window.SetQuitOnClose(true);
|
||||||
|
|
||||||
|
::MSG msg;
|
||||||
|
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||||
|
::TranslateMessage(&msg);
|
||||||
|
::DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
::CoUninitialize();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
16
filcnaplo/windows/runner/resource.h
Normal file
16
filcnaplo/windows/runner/resource.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//{{NO_DEPENDENCIES}}
|
||||||
|
// Microsoft Visual C++ generated include file.
|
||||||
|
// Used by Runner.rc
|
||||||
|
//
|
||||||
|
#define IDI_APP_ICON 101
|
||||||
|
|
||||||
|
// Next default values for new objects
|
||||||
|
//
|
||||||
|
#ifdef APSTUDIO_INVOKED
|
||||||
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||||
|
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||||
|
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||||
|
#define _APS_NEXT_SYMED_VALUE 101
|
||||||
|
#endif
|
||||||
|
#endif
|
BIN
filcnaplo/windows/runner/resources/app_icon.ico
Normal file
BIN
filcnaplo/windows/runner/resources/app_icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
20
filcnaplo/windows/runner/runner.exe.manifest
Normal file
20
filcnaplo/windows/runner/runner.exe.manifest
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows 10 and Windows 11 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
65
filcnaplo/windows/runner/utils.cpp
Normal file
65
filcnaplo/windows/runner/utils.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <flutter_windows.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void CreateAndAttachConsole() {
|
||||||
|
if (::AllocConsole()) {
|
||||||
|
FILE *unused;
|
||||||
|
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||||
|
_dup2(_fileno(stdout), 1);
|
||||||
|
}
|
||||||
|
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||||
|
_dup2(_fileno(stdout), 2);
|
||||||
|
}
|
||||||
|
std::ios::sync_with_stdio();
|
||||||
|
FlutterDesktopResyncOutputStreams();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> GetCommandLineArguments() {
|
||||||
|
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
||||||
|
int argc;
|
||||||
|
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||||
|
if (argv == nullptr) {
|
||||||
|
return std::vector<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> command_line_arguments;
|
||||||
|
|
||||||
|
// Skip the first argument as it's the binary name.
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
::LocalFree(argv);
|
||||||
|
|
||||||
|
return command_line_arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
||||||
|
if (utf16_string == nullptr) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
int target_length = ::WideCharToMultiByte(
|
||||||
|
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||||
|
-1, nullptr, 0, nullptr, nullptr)
|
||||||
|
-1; // remove the trailing null character
|
||||||
|
int input_length = (int)wcslen(utf16_string);
|
||||||
|
std::string utf8_string;
|
||||||
|
if (target_length <= 0 || target_length > utf8_string.max_size()) {
|
||||||
|
return utf8_string;
|
||||||
|
}
|
||||||
|
utf8_string.resize(target_length);
|
||||||
|
int converted_length = ::WideCharToMultiByte(
|
||||||
|
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||||
|
input_length, utf8_string.data(), target_length, nullptr, nullptr);
|
||||||
|
if (converted_length == 0) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
return utf8_string;
|
||||||
|
}
|
19
filcnaplo/windows/runner/utils.h
Normal file
19
filcnaplo/windows/runner/utils.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef RUNNER_UTILS_H_
|
||||||
|
#define RUNNER_UTILS_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Creates a console for the process, and redirects stdout and stderr to
|
||||||
|
// it for both the runner and the Flutter library.
|
||||||
|
void CreateAndAttachConsole();
|
||||||
|
|
||||||
|
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
||||||
|
// encoded in UTF-8. Returns an empty std::string on failure.
|
||||||
|
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
||||||
|
|
||||||
|
// Gets the command line arguments passed in as a std::vector<std::string>,
|
||||||
|
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
||||||
|
std::vector<std::string> GetCommandLineArguments();
|
||||||
|
|
||||||
|
#endif // RUNNER_UTILS_H_
|
288
filcnaplo/windows/runner/win32_window.cpp
Normal file
288
filcnaplo/windows/runner/win32_window.cpp
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
#include "win32_window.h"
|
||||||
|
|
||||||
|
#include <dwmapi.h>
|
||||||
|
#include <flutter_windows.h>
|
||||||
|
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/// Window attribute that enables dark mode window decorations.
|
||||||
|
///
|
||||||
|
/// Redefined in case the developer's machine has a Windows SDK older than
|
||||||
|
/// version 10.0.22000.0.
|
||||||
|
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||||
|
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||||
|
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||||
|
|
||||||
|
/// Registry key for app theme preference.
|
||||||
|
///
|
||||||
|
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
|
||||||
|
/// value indicates apps should use light mode.
|
||||||
|
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
|
||||||
|
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||||||
|
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
|
||||||
|
|
||||||
|
// The number of Win32Window objects that currently exist.
|
||||||
|
static int g_active_window_count = 0;
|
||||||
|
|
||||||
|
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||||
|
|
||||||
|
// Scale helper to convert logical scaler values to physical using passed in
|
||||||
|
// scale factor
|
||||||
|
int Scale(int source, double scale_factor) {
|
||||||
|
return static_cast<int>(source * scale_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||||
|
// This API is only needed for PerMonitor V1 awareness mode.
|
||||||
|
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||||
|
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||||
|
if (!user32_module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto enable_non_client_dpi_scaling =
|
||||||
|
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||||
|
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||||
|
if (enable_non_client_dpi_scaling != nullptr) {
|
||||||
|
enable_non_client_dpi_scaling(hwnd);
|
||||||
|
}
|
||||||
|
FreeLibrary(user32_module);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Manages the Win32Window's window class registration.
|
||||||
|
class WindowClassRegistrar {
|
||||||
|
public:
|
||||||
|
~WindowClassRegistrar() = default;
|
||||||
|
|
||||||
|
// Returns the singleton registrar instance.
|
||||||
|
static WindowClassRegistrar* GetInstance() {
|
||||||
|
if (!instance_) {
|
||||||
|
instance_ = new WindowClassRegistrar();
|
||||||
|
}
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the name of the window class, registering the class if it hasn't
|
||||||
|
// previously been registered.
|
||||||
|
const wchar_t* GetWindowClass();
|
||||||
|
|
||||||
|
// Unregisters the window class. Should only be called if there are no
|
||||||
|
// instances of the window.
|
||||||
|
void UnregisterWindowClass();
|
||||||
|
|
||||||
|
private:
|
||||||
|
WindowClassRegistrar() = default;
|
||||||
|
|
||||||
|
static WindowClassRegistrar* instance_;
|
||||||
|
|
||||||
|
bool class_registered_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||||
|
|
||||||
|
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||||
|
if (!class_registered_) {
|
||||||
|
WNDCLASS window_class{};
|
||||||
|
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||||
|
window_class.lpszClassName = kWindowClassName;
|
||||||
|
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||||
|
window_class.cbClsExtra = 0;
|
||||||
|
window_class.cbWndExtra = 0;
|
||||||
|
window_class.hInstance = GetModuleHandle(nullptr);
|
||||||
|
window_class.hIcon =
|
||||||
|
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||||
|
window_class.hbrBackground = 0;
|
||||||
|
window_class.lpszMenuName = nullptr;
|
||||||
|
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||||
|
RegisterClass(&window_class);
|
||||||
|
class_registered_ = true;
|
||||||
|
}
|
||||||
|
return kWindowClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||||
|
UnregisterClass(kWindowClassName, nullptr);
|
||||||
|
class_registered_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32Window::Win32Window() {
|
||||||
|
++g_active_window_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32Window::~Win32Window() {
|
||||||
|
--g_active_window_count;
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32Window::Create(const std::wstring& title,
|
||||||
|
const Point& origin,
|
||||||
|
const Size& size) {
|
||||||
|
Destroy();
|
||||||
|
|
||||||
|
const wchar_t* window_class =
|
||||||
|
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||||
|
|
||||||
|
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||||
|
static_cast<LONG>(origin.y)};
|
||||||
|
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||||
|
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||||
|
double scale_factor = dpi / 96.0;
|
||||||
|
|
||||||
|
HWND window = CreateWindow(
|
||||||
|
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
|
||||||
|
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||||
|
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||||
|
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTheme(window);
|
||||||
|
|
||||||
|
return OnCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32Window::Show() {
|
||||||
|
return ShowWindow(window_handle_, SW_SHOWNORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||||
|
UINT const message,
|
||||||
|
WPARAM const wparam,
|
||||||
|
LPARAM const lparam) noexcept {
|
||||||
|
if (message == WM_NCCREATE) {
|
||||||
|
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||||
|
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||||
|
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||||
|
|
||||||
|
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||||
|
EnableFullDpiSupportIfAvailable(window);
|
||||||
|
that->window_handle_ = window;
|
||||||
|
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||||
|
return that->MessageHandler(window, message, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(window, message, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT
|
||||||
|
Win32Window::MessageHandler(HWND hwnd,
|
||||||
|
UINT const message,
|
||||||
|
WPARAM const wparam,
|
||||||
|
LPARAM const lparam) noexcept {
|
||||||
|
switch (message) {
|
||||||
|
case WM_DESTROY:
|
||||||
|
window_handle_ = nullptr;
|
||||||
|
Destroy();
|
||||||
|
if (quit_on_close_) {
|
||||||
|
PostQuitMessage(0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_DPICHANGED: {
|
||||||
|
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||||
|
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||||
|
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||||
|
|
||||||
|
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||||
|
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case WM_SIZE: {
|
||||||
|
RECT rect = GetClientArea();
|
||||||
|
if (child_content_ != nullptr) {
|
||||||
|
// Size and position the child window.
|
||||||
|
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||||
|
rect.bottom - rect.top, TRUE);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_ACTIVATE:
|
||||||
|
if (child_content_ != nullptr) {
|
||||||
|
SetFocus(child_content_);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
||||||
|
UpdateTheme(hwnd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32Window::Destroy() {
|
||||||
|
OnDestroy();
|
||||||
|
|
||||||
|
if (window_handle_) {
|
||||||
|
DestroyWindow(window_handle_);
|
||||||
|
window_handle_ = nullptr;
|
||||||
|
}
|
||||||
|
if (g_active_window_count == 0) {
|
||||||
|
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||||
|
return reinterpret_cast<Win32Window*>(
|
||||||
|
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32Window::SetChildContent(HWND content) {
|
||||||
|
child_content_ = content;
|
||||||
|
SetParent(content, window_handle_);
|
||||||
|
RECT frame = GetClientArea();
|
||||||
|
|
||||||
|
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||||
|
frame.bottom - frame.top, true);
|
||||||
|
|
||||||
|
SetFocus(child_content_);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT Win32Window::GetClientArea() {
|
||||||
|
RECT frame;
|
||||||
|
GetClientRect(window_handle_, &frame);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND Win32Window::GetHandle() {
|
||||||
|
return window_handle_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||||
|
quit_on_close_ = quit_on_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32Window::OnCreate() {
|
||||||
|
// No-op; provided for subclasses.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32Window::OnDestroy() {
|
||||||
|
// No-op; provided for subclasses.
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32Window::UpdateTheme(HWND const window) {
|
||||||
|
DWORD light_mode;
|
||||||
|
DWORD light_mode_size = sizeof(light_mode);
|
||||||
|
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
|
||||||
|
kGetPreferredBrightnessRegValue,
|
||||||
|
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
||||||
|
&light_mode_size);
|
||||||
|
|
||||||
|
if (result == ERROR_SUCCESS) {
|
||||||
|
BOOL enable_dark_mode = light_mode == 0;
|
||||||
|
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||||
|
&enable_dark_mode, sizeof(enable_dark_mode));
|
||||||
|
}
|
||||||
|
}
|
102
filcnaplo/windows/runner/win32_window.h
Normal file
102
filcnaplo/windows/runner/win32_window.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#ifndef RUNNER_WIN32_WINDOW_H_
|
||||||
|
#define RUNNER_WIN32_WINDOW_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
||||||
|
// inherited from by classes that wish to specialize with custom
|
||||||
|
// rendering and input handling
|
||||||
|
class Win32Window {
|
||||||
|
public:
|
||||||
|
struct Point {
|
||||||
|
unsigned int x;
|
||||||
|
unsigned int y;
|
||||||
|
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Size {
|
||||||
|
unsigned int width;
|
||||||
|
unsigned int height;
|
||||||
|
Size(unsigned int width, unsigned int height)
|
||||||
|
: width(width), height(height) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Win32Window();
|
||||||
|
virtual ~Win32Window();
|
||||||
|
|
||||||
|
// Creates a win32 window with |title| that is positioned and sized using
|
||||||
|
// |origin| and |size|. New windows are created on the default monitor. Window
|
||||||
|
// sizes are specified to the OS in physical pixels, hence to ensure a
|
||||||
|
// consistent size this function will scale the inputted width and height as
|
||||||
|
// as appropriate for the default monitor. The window is invisible until
|
||||||
|
// |Show| is called. Returns true if the window was created successfully.
|
||||||
|
bool Create(const std::wstring& title, const Point& origin, const Size& size);
|
||||||
|
|
||||||
|
// Show the current window. Returns true if the window was successfully shown.
|
||||||
|
bool Show();
|
||||||
|
|
||||||
|
// Release OS resources associated with window.
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
// Inserts |content| into the window tree.
|
||||||
|
void SetChildContent(HWND content);
|
||||||
|
|
||||||
|
// Returns the backing Window handle to enable clients to set icon and other
|
||||||
|
// window properties. Returns nullptr if the window has been destroyed.
|
||||||
|
HWND GetHandle();
|
||||||
|
|
||||||
|
// If true, closing this window will quit the application.
|
||||||
|
void SetQuitOnClose(bool quit_on_close);
|
||||||
|
|
||||||
|
// Return a RECT representing the bounds of the current client area.
|
||||||
|
RECT GetClientArea();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Processes and route salient window messages for mouse handling,
|
||||||
|
// size change and DPI. Delegates handling of these to member overloads that
|
||||||
|
// inheriting classes can handle.
|
||||||
|
virtual LRESULT MessageHandler(HWND window,
|
||||||
|
UINT const message,
|
||||||
|
WPARAM const wparam,
|
||||||
|
LPARAM const lparam) noexcept;
|
||||||
|
|
||||||
|
// Called when CreateAndShow is called, allowing subclass window-related
|
||||||
|
// setup. Subclasses should return false if setup fails.
|
||||||
|
virtual bool OnCreate();
|
||||||
|
|
||||||
|
// Called when Destroy is called.
|
||||||
|
virtual void OnDestroy();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class WindowClassRegistrar;
|
||||||
|
|
||||||
|
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
||||||
|
// is passed when the non-client area is being created and enables automatic
|
||||||
|
// non-client DPI scaling so that the non-client area automatically
|
||||||
|
// responds to changes in DPI. All other messages are handled by
|
||||||
|
// MessageHandler.
|
||||||
|
static LRESULT CALLBACK WndProc(HWND const window,
|
||||||
|
UINT const message,
|
||||||
|
WPARAM const wparam,
|
||||||
|
LPARAM const lparam) noexcept;
|
||||||
|
|
||||||
|
// Retrieves a class instance pointer for |window|
|
||||||
|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
||||||
|
|
||||||
|
// Update the window frame's theme to match the system theme.
|
||||||
|
static void UpdateTheme(HWND const window);
|
||||||
|
|
||||||
|
bool quit_on_close_ = false;
|
||||||
|
|
||||||
|
// window handle for top level window.
|
||||||
|
HWND window_handle_ = nullptr;
|
||||||
|
|
||||||
|
// window handle for hosted content.
|
||||||
|
HWND child_content_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RUNNER_WIN32_WINDOW_H_
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||||
import 'package:filcnaplo/models/settings.dart';
|
import 'package:filcnaplo/models/settings.dart';
|
||||||
import 'package:filcnaplo/ui/date_widget.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:flutter/material.dart';
|
||||||
import 'package:animated_list_plus/animated_list_plus.dart';
|
import 'package:animated_list_plus/animated_list_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -16,7 +16,8 @@ class HomePage extends StatefulWidget {
|
|||||||
State<HomePage> createState() => _HomePageState();
|
State<HomePage> createState() => _HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
|
class _HomePageState extends State<HomePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
late UserProvider user;
|
late UserProvider user;
|
||||||
late SettingsProvider settings;
|
late SettingsProvider settings;
|
||||||
|
|
||||||
@ -41,11 +42,15 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
|||||||
user = Provider.of<UserProvider>(context, listen: false);
|
user = Provider.of<UserProvider>(context, listen: false);
|
||||||
|
|
||||||
DateTime now = DateTime.now();
|
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";
|
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";
|
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";
|
greeting = "merryxmas";
|
||||||
} else if (now.month == DateTime.january && now.day == 1) {
|
} else if (now.month == DateTime.january && now.day == 1) {
|
||||||
greeting = "happynewyear";
|
greeting = "happynewyear";
|
||||||
@ -81,7 +86,8 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
|||||||
children: [
|
children: [
|
||||||
// Greeting
|
// Greeting
|
||||||
Padding(
|
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(
|
child: Text(
|
||||||
greeting.i18n.fill([firstName]),
|
greeting.i18n.fill([firstName]),
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
@ -107,8 +113,10 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
|||||||
int selectedPage = _pageController.page!.round();
|
int selectedPage = _pageController.page!.round();
|
||||||
|
|
||||||
if (i == selectedPage) return;
|
if (i == selectedPage) return;
|
||||||
if (_pageController.page?.roundToDouble() != _pageController.page) {
|
if (_pageController.page?.roundToDouble() !=
|
||||||
_pageController.animateToPage(i, curve: Curves.easeIn, duration: kTabScrollDuration);
|
_pageController.page) {
|
||||||
|
_pageController.animateToPage(i,
|
||||||
|
curve: Curves.easeIn, duration: kTabScrollDuration);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,27 +140,34 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
|
|||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
return FutureBuilder<List<DateWidget>>(
|
return FutureBuilder<List<DateWidget>>(
|
||||||
key: ValueKey<String>(listOrder[index]),
|
key: ValueKey<String>(listOrder[index]),
|
||||||
future: getFilterWidgets(homeFilters[index], context: context),
|
future: getFilterWidgets(homeFilters[index],
|
||||||
builder: (context, dateWidgets) => dateWidgets.data != null
|
context: context),
|
||||||
|
builder: (context, dateWidgets) => dateWidgets.data !=
|
||||||
|
null
|
||||||
? ImplicitlyAnimatedList<Widget>(
|
? ImplicitlyAnimatedList<Widget>(
|
||||||
items: sortDateWidgets(context, dateWidgets: dateWidgets.data!),
|
items: sortDateWidgets(context,
|
||||||
|
dateWidgets: dateWidgets.data!),
|
||||||
itemBuilder: filterItemBuilder,
|
itemBuilder: filterItemBuilder,
|
||||||
spawnIsolate: false,
|
spawnIsolate: false,
|
||||||
areItemsTheSame: (a, b) => a.key == b.key,
|
areItemsTheSame: (a, b) => a.key == b.key,
|
||||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
physics: const BouncingScrollPhysics(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
parent: AlwaysScrollableScrollPhysics()),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24.0),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount: 4,
|
childCount: 4,
|
||||||
findChildIndexCallback: (Key key) {
|
findChildIndexCallback: (Key key) {
|
||||||
final ValueKey<String> valueKey = key as ValueKey<String>;
|
final ValueKey<String> valueKey =
|
||||||
|
key as ValueKey<String>;
|
||||||
final String data = valueKey.value;
|
final String data = valueKey.value;
|
||||||
return listOrder.indexOf(data);
|
return listOrder.indexOf(data);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
physics: const PageScrollPhysics().applyTo(const BouncingScrollPhysics()),
|
physics: const PageScrollPhysics()
|
||||||
|
.applyTo(const BouncingScrollPhysics()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -23,12 +23,14 @@ import 'timetable_page.i18n.dart';
|
|||||||
// todo: "fix" overflow (priority: -1)
|
// todo: "fix" overflow (priority: -1)
|
||||||
|
|
||||||
class TimetablePage extends StatefulWidget {
|
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 DateTime? initialDay;
|
||||||
final Week? initialWeek;
|
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
|
// Go to timetable page with arguments
|
||||||
// NavigationScreen.of(context)?.customRoute(navigationPageRoute((context) => TimetablePage(
|
// NavigationScreen.of(context)?.customRoute(navigationPageRoute((context) => TimetablePage(
|
||||||
// initialDay: lesson?.date ?? day,
|
// initialDay: lesson?.date ?? day,
|
||||||
@ -49,7 +51,8 @@ class TimetablePage extends StatefulWidget {
|
|||||||
_TimetablePageState createState() => _TimetablePageState();
|
_TimetablePageState createState() => _TimetablePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TimetablePageState extends State<TimetablePage> with TickerProviderStateMixin {
|
class _TimetablePageState extends State<TimetablePage>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
late UserProvider user;
|
late UserProvider user;
|
||||||
late TimetableProvider timetableProvider;
|
late TimetableProvider timetableProvider;
|
||||||
late UpdateProvider updateProvider;
|
late UpdateProvider updateProvider;
|
||||||
@ -60,7 +63,9 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
|
|
||||||
int _getDayIndex(DateTime date) {
|
int _getDayIndex(DateTime date) {
|
||||||
int index = 0;
|
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
|
// find the first day with upcoming lessons
|
||||||
index = _controller.days!.indexWhere((day) => day.last.end.isAfter(date));
|
index = _controller.days!.indexWhere((day) => day.last.end.isAfter(date));
|
||||||
@ -94,11 +99,14 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
length: _controller.days!.length,
|
length: _controller.days!.length,
|
||||||
vsync: this,
|
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) {
|
if (initial ||
|
||||||
_tabController.animateTo(_getDayIndex(widget.initialDay ?? DateTime.now()));
|
_controller.previousWeekId != _controller.currentWeekId) {
|
||||||
|
_tabController
|
||||||
|
.animateTo(_getDayIndex(widget.initialDay ?? DateTime.now()));
|
||||||
}
|
}
|
||||||
initial = false;
|
initial = false;
|
||||||
|
|
||||||
@ -111,7 +119,8 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
if (widget.initialWeek != null) {
|
if (widget.initialWeek != null) {
|
||||||
_controller.jump(widget.initialWeek!, context: context, initial: true);
|
_controller.jump(widget.initialWeek!, context: context, initial: true);
|
||||||
} else {
|
} 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
|
// Listen for user changes
|
||||||
@ -131,7 +140,8 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
// Sometimes when changing weeks really fast,
|
// Sometimes when changing weeks really fast,
|
||||||
// controller.days might be null or won't include index
|
// controller.days might be null or won't include index
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
return "timetable".i18n;
|
return "timetable".i18n;
|
||||||
}
|
}
|
||||||
@ -181,9 +191,14 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
children: [
|
children: [
|
||||||
// Day Title
|
// Day Title
|
||||||
Padding(
|
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(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
dayTitle(tab).capital(),
|
dayTitle(tab).capital(),
|
||||||
@ -193,9 +208,13 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"${_controller.days![tab].first.date.day}".padLeft(2, '0') + ".",
|
"${_controller.days![tab].first.date.day}"
|
||||||
|
.padLeft(2, '0') +
|
||||||
|
".",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.of(context).text.withOpacity(.5),
|
color: AppColors.of(context)
|
||||||
|
.text
|
||||||
|
.withOpacity(.5),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -208,35 +227,59 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
itemCount: _controller.days![tab].length + 2,
|
itemCount:
|
||||||
|
_controller.days![tab].length + 2,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (_controller.days == null) return Container();
|
if (_controller.days == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.only(top: 8.0, left: 24.0, right: 24.0),
|
padding: EdgeInsets.only(
|
||||||
child: PanelHeader(padding: EdgeInsets.only(top: 12.0)),
|
top: 8.0,
|
||||||
|
left: 24.0,
|
||||||
|
right: 24.0),
|
||||||
|
child: PanelHeader(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 12.0)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
if (index == _controller.days![tab].length + 1) {
|
if (index ==
|
||||||
|
_controller.days![tab].length +
|
||||||
|
1) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.only(bottom: 8.0, left: 24.0, right: 24.0),
|
padding: EdgeInsets.only(
|
||||||
child: PanelFooter(padding: EdgeInsets.only(top: 12.0)),
|
bottom: 8.0,
|
||||||
|
left: 24.0,
|
||||||
|
right: 24.0),
|
||||||
|
child: PanelFooter(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 12.0)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
final Lesson lesson = _controller.days![tab][index - 1];
|
final Lesson lesson =
|
||||||
final bool swapDescDay = _controller.days![tab].map((l) => l.swapDesc ? 1 : 0).reduce((a, b) => a + b) >=
|
_controller.days![tab][index - 1];
|
||||||
_controller.days![tab].length * .5;
|
final bool swapDescDay = _controller
|
||||||
|
.days![tab]
|
||||||
|
.map(
|
||||||
|
(l) => l.swapDesc ? 1 : 0)
|
||||||
|
.reduce((a, b) => a + b) >=
|
||||||
|
_controller.days![tab].length *
|
||||||
|
.5;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24.0),
|
||||||
child: PanelBody(
|
child: PanelBody(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10.0),
|
||||||
child: LessonViewable(
|
child: LessonViewable(
|
||||||
lesson,
|
lesson,
|
||||||
swapDesc: swapDescDay,
|
swapDesc: swapDescDay,
|
||||||
@ -264,7 +307,8 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -285,7 +329,10 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
onTap: () => setState(() {
|
onTap: () => setState(() {
|
||||||
_controller.current();
|
_controller.current();
|
||||||
if (mounted) {
|
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(
|
child: Padding(
|
||||||
@ -295,12 +342,22 @@ class _TimetablePageState extends State<TimetablePage> with TickerProviderStateM
|
|||||||
"week".i18n +
|
"week".i18n +
|
||||||
" (" +
|
" (" +
|
||||||
// Week start
|
// 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)
|
I18n.of(context).locale.languageCode)
|
||||||
.format(_controller.currentWeek.start) +
|
.format(_controller.currentWeek.start) +
|
||||||
" - " +
|
" - " +
|
||||||
// Week end
|
// 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)
|
I18n.of(context).locale.languageCode)
|
||||||
.format(_controller.currentWeek.end) +
|
.format(_controller.currentWeek.end) +
|
||||||
")",
|
")",
|
||||||
|
@ -6,6 +6,7 @@ import 'package:filcnaplo/models/settings.dart';
|
|||||||
import 'package:filcnaplo/utils/color.dart';
|
import 'package:filcnaplo/utils/color.dart';
|
||||||
import 'package:filcnaplo_desktop_ui/common/panel_button.dart';
|
import 'package:filcnaplo_desktop_ui/common/panel_button.dart';
|
||||||
import 'package:filcnaplo_desktop_ui/common/profile_image.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/navigation/sidebar_action.dart';
|
||||||
import 'package:filcnaplo_desktop_ui/screens/settings/settings_screen.dart';
|
import 'package:filcnaplo_desktop_ui/screens/settings/settings_screen.dart';
|
||||||
import 'package:filcnaplo_mobile_ui/screens/settings/accounts/account_tile.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';
|
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||||
|
|
||||||
class Sidebar extends StatefulWidget {
|
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 NavigatorState navigator;
|
||||||
final String selected;
|
final String selected;
|
||||||
@ -71,12 +77,12 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
if (!settings.presentationMode) {
|
if (!settings.presentationMode) {
|
||||||
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
||||||
} else {
|
} else {
|
||||||
firstName = "Béla";
|
firstName = "János";
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> pageWidgets = [
|
List<Widget> pageWidgets = [
|
||||||
SidebarAction(
|
SidebarAction(
|
||||||
title: const Text("Home"),
|
title: Text("Home".i18n),
|
||||||
icon: const Icon(FilcIcons.home),
|
icon: const Icon(FilcIcons.home),
|
||||||
selected: widget.selected == "home",
|
selected: widget.selected == "home",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -87,7 +93,7 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SidebarAction(
|
SidebarAction(
|
||||||
title: const Text("Grades"),
|
title: Text("Grades".i18n),
|
||||||
icon: const Icon(FeatherIcons.bookmark),
|
icon: const Icon(FeatherIcons.bookmark),
|
||||||
selected: widget.selected == "grades",
|
selected: widget.selected == "grades",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -98,7 +104,7 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SidebarAction(
|
SidebarAction(
|
||||||
title: const Text("Timetable"),
|
title: Text("Timetable".i18n),
|
||||||
icon: const Icon(FeatherIcons.calendar),
|
icon: const Icon(FeatherIcons.calendar),
|
||||||
selected: widget.selected == "timetable",
|
selected: widget.selected == "timetable",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -109,7 +115,7 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SidebarAction(
|
SidebarAction(
|
||||||
title: const Text("Messages"),
|
title: Text("Messages".i18n),
|
||||||
icon: const Icon(FeatherIcons.messageSquare),
|
icon: const Icon(FeatherIcons.messageSquare),
|
||||||
selected: widget.selected == "messages",
|
selected: widget.selected == "messages",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -120,7 +126,7 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SidebarAction(
|
SidebarAction(
|
||||||
title: const Text("Absences"),
|
title: Text("Absences".i18n),
|
||||||
icon: const Icon(FeatherIcons.clock),
|
icon: const Icon(FeatherIcons.clock),
|
||||||
selected: widget.selected == "absences",
|
selected: widget.selected == "absences",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -134,12 +140,15 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
|
|
||||||
List<Widget> bottomActions = [
|
List<Widget> bottomActions = [
|
||||||
SidebarAction(
|
SidebarAction(
|
||||||
title: const Text("Settings"),
|
title: Text("Settings".i18n),
|
||||||
selected: true,
|
selected: true,
|
||||||
icon: const Icon(FeatherIcons.settings),
|
icon: const Icon(FeatherIcons.settings),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (topNav != "settings") {
|
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";
|
topNav = "settings";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -159,7 +168,7 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pushNamed("login_back");
|
Navigator.of(context).pushNamed("login_back");
|
||||||
},
|
},
|
||||||
title: const Text("Add User"),
|
title: Text("adduser".i18n),
|
||||||
leading: const Icon(FeatherIcons.userPlus),
|
leading: const Icon(FeatherIcons.userPlus),
|
||||||
),
|
),
|
||||||
PanelButton(
|
PanelButton(
|
||||||
@ -169,17 +178,20 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
|
|
||||||
// Delete User
|
// Delete User
|
||||||
user.removeUser(userId);
|
user.removeUser(userId);
|
||||||
await Provider.of<DatabaseProvider>(context, listen: false).store.removeUser(userId);
|
await Provider.of<DatabaseProvider>(context, listen: false)
|
||||||
|
.store
|
||||||
|
.removeUser(userId);
|
||||||
|
|
||||||
// If no other Users left, go back to LoginScreen
|
// If no other Users left, go back to LoginScreen
|
||||||
if (user.getUsers().isNotEmpty) {
|
if (user.getUsers().isNotEmpty) {
|
||||||
user.setUser(user.getUsers().first.id);
|
user.setUser(user.getUsers().first.id);
|
||||||
restore().then((_) => user.setUser(user.getUsers().first.id));
|
restore().then((_) => user.setUser(user.getUsers().first.id));
|
||||||
} else {
|
} 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),
|
leading: Icon(FeatherIcons.logOut, color: AppColors.of(context).red),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -190,49 +202,77 @@ class _SidebarState extends State<Sidebar> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 18.0, top: 18.0, bottom: 24.0, right: 8.0),
|
padding: const EdgeInsets.only(
|
||||||
child: Row(
|
left: 12.0,
|
||||||
children: [
|
top: 18.0,
|
||||||
Padding(
|
bottom: 24.0,
|
||||||
padding: const EdgeInsets.only(right: 12.0),
|
right: 12.0,
|
||||||
child: ProfileImage(
|
),
|
||||||
name: firstName,
|
child: InkWell(
|
||||||
radius: 18.0,
|
customBorder: RoundedRectangleBorder(
|
||||||
backgroundColor:
|
borderRadius: BorderRadius.circular(10)),
|
||||||
!settings.presentationMode ? ColorUtils.stringToColor(user.name ?? "?") : Theme.of(context).colorScheme.secondary,
|
onTap: () {
|
||||||
),
|
setState(() {
|
||||||
),
|
expandAccount = !expandAccount;
|
||||||
Expanded(
|
});
|
||||||
child: Text(
|
},
|
||||||
firstName,
|
child: Row(
|
||||||
style: const TextStyle(
|
children: [
|
||||||
fontSize: 16.0,
|
Padding(
|
||||||
fontWeight: FontWeight.w600,
|
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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
PageTransitionSwitcher(
|
child: Text(
|
||||||
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
|
firstName,
|
||||||
return FadeThroughTransition(
|
style: const TextStyle(
|
||||||
fillColor: Colors.transparent,
|
fontSize: 16.0,
|
||||||
animation: primaryAnimation,
|
fontWeight: FontWeight.w600,
|
||||||
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;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
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<Sidebar> {
|
|||||||
if (!settings.presentationMode) {
|
if (!settings.presentationMode) {
|
||||||
_firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0];
|
_firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0];
|
||||||
} else {
|
} else {
|
||||||
_firstName = "Béla";
|
_firstName = "János";
|
||||||
}
|
}
|
||||||
|
|
||||||
accountTiles.add(AccountTile(
|
accountTiles.add(AccountTile(
|
||||||
name: Text(!settings.presentationMode ? account.name : "Béla", style: const TextStyle(fontWeight: FontWeight.w500)),
|
name: Text(!settings.presentationMode ? account.name : "János",
|
||||||
username: Text(!settings.presentationMode ? account.username : "72469696969"),
|
style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||||
|
username:
|
||||||
|
Text(!settings.presentationMode ? account.username : "72469696969"),
|
||||||
profileImage: ProfileImage(
|
profileImage: ProfileImage(
|
||||||
name: _firstName,
|
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,
|
role: account.role,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:i18n_extension/i18n_extension.dart';
|
||||||
|
|
||||||
|
extension SettingsLocalization on String {
|
||||||
|
static final _t = Translations.byLocale("hu_hu") +
|
||||||
|
{
|
||||||
|
"en_en": {
|
||||||
|
"Home": "Home",
|
||||||
|
"Grades": "Grades",
|
||||||
|
"Timetable": "Timetable",
|
||||||
|
"Messages": "Messages",
|
||||||
|
"Absences": "Absences",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"adduser": "Add User",
|
||||||
|
"logout": "Log Out",
|
||||||
|
},
|
||||||
|
"hu_hu": {
|
||||||
|
"Home": "Kezdőlap",
|
||||||
|
"Grades": "Jegyek",
|
||||||
|
"Timetable": "Órarend",
|
||||||
|
"Messages": "Üzenetek",
|
||||||
|
"Absences": "Hiányzások",
|
||||||
|
"Settings": "Beállítások",
|
||||||
|
"adduser": "Fiók hozzáadása",
|
||||||
|
"logout": "Kilépés",
|
||||||
|
},
|
||||||
|
"de_de": {
|
||||||
|
"Home": "Zuhause",
|
||||||
|
"Grades": "Noten",
|
||||||
|
"Timetable": "Zeitplan",
|
||||||
|
"Messages": "Mitteilungen",
|
||||||
|
"Absences": "Fehlen",
|
||||||
|
"Settings": "Einstellungen",
|
||||||
|
"adduser": "Benutzer hinzufügen",
|
||||||
|
"logout": "Abmelden",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
String get i18n => localize(this, _t);
|
||||||
|
String fill(List<Object> params) => localizeFill(this, params);
|
||||||
|
String plural(int value) => localizePlural(value, this, _t);
|
||||||
|
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@ dependencies:
|
|||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
flutter_acrylic: ^1.1.3
|
flutter_acrylic: ^1.1.3
|
||||||
elegant_notification: ^1.6.1
|
elegant_notification: ^1.6.1
|
||||||
|
flutter_staggered_grid_view: ^0.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
|
@ -169,13 +169,13 @@ class _PersonalityCardState extends State<PersonalityCard> {
|
|||||||
} else if (mostCommonGrade.keys.toList()[0] == 1 &&
|
} else if (mostCommonGrade.keys.toList()[0] == 1 &&
|
||||||
mostCommonGrade.values.toList()[0] > 1) {
|
mostCommonGrade.values.toList()[0] > 1) {
|
||||||
finalPersonality = PersonalityType.fallible;
|
finalPersonality = PersonalityType.fallible;
|
||||||
} else if (absences.length < 10) {
|
} else if (absences.length <= 12) {
|
||||||
finalPersonality = PersonalityType.healthy;
|
finalPersonality = PersonalityType.healthy;
|
||||||
} else if (unexcusedAbsences >= 10) {
|
} else if (unexcusedAbsences >= 8) {
|
||||||
finalPersonality = PersonalityType.quitter;
|
finalPersonality = PersonalityType.quitter;
|
||||||
} else if (totalDelays > 50) {
|
} else if (totalDelays > 50) {
|
||||||
finalPersonality = PersonalityType.late;
|
finalPersonality = PersonalityType.late;
|
||||||
} else if (absences.length >= 100) {
|
} else if (absences.length >= 120) {
|
||||||
finalPersonality = PersonalityType.sick;
|
finalPersonality = PersonalityType.sick;
|
||||||
} else if (mostCommonGrade.keys.toList()[0] == 2) {
|
} else if (mostCommonGrade.keys.toList()[0] == 2) {
|
||||||
finalPersonality = PersonalityType.acceptable;
|
finalPersonality = PersonalityType.acceptable;
|
||||||
|
@ -8,7 +8,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'absence_group_tile.i18n.dart';
|
import 'absence_group_tile.i18n.dart';
|
||||||
|
|
||||||
class AbsenceGroupTile extends StatelessWidget {
|
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<AbsenceViewable> absences;
|
final List<AbsenceViewable> absences;
|
||||||
final bool showDate;
|
final bool showDate;
|
||||||
@ -16,10 +18,12 @@ class AbsenceGroupTile extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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);
|
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(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(14.0),
|
borderRadius: BorderRadius.circular(14.0),
|
||||||
@ -29,6 +33,8 @@ class AbsenceGroupTile extends StatelessWidget {
|
|||||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: AbsenceGroupContainer(
|
child: AbsenceGroupContainer(
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10)),
|
||||||
tilePadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
tilePadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
leading: Container(
|
leading: Container(
|
||||||
@ -38,22 +44,33 @@ class AbsenceGroupTile extends StatelessWidget {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: color.withOpacity(.25),
|
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(
|
title: Text.rich(TextSpan(
|
||||||
text: "${absences.where((a) => a.absence.state == state).length} ",
|
text:
|
||||||
style: TextStyle(fontWeight: FontWeight.w700, color: AppColors.of(context).text),
|
"${absences.where((a) => a.absence.state == state).length} ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColors.of(context).text),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: AbsenceTile.justificationName(state).fill(["absence".i18n]),
|
text: AbsenceTile.justificationName(state)
|
||||||
style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.of(context).text),
|
.fill(["absence".i18n]),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.of(context).text),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
subtitle: showDate
|
subtitle: showDate
|
||||||
? Text(
|
? Text(
|
||||||
absences.first.absence.date.format(context, weekday: true),
|
absences.first.absence.date
|
||||||
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.8)),
|
.format(context, weekday: true),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.of(context).text.withOpacity(0.8)),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
children: absences,
|
children: absences,
|
||||||
|
@ -118,10 +118,13 @@ class _AllSumBodyState extends State<AllSumBody> {
|
|||||||
|
|
||||||
void getDelays() {
|
void getDelays() {
|
||||||
var allDelays = absenceProvider.absences.where((a) => a.delay > 0);
|
var allDelays = absenceProvider.absences.where((a) => a.delay > 0);
|
||||||
var totalDelayTime = (allDelays.map((a) {
|
var delayTimeList = (allDelays.map((a) {
|
||||||
return a.delay;
|
return a.delay;
|
||||||
}).toList())
|
}).toList());
|
||||||
.reduce((a, b) => a + b);
|
var totalDelayTime = 0;
|
||||||
|
if (delayTimeList.isNotEmpty) {
|
||||||
|
totalDelayTime = delayTimeList.reduce((a, b) => a + b);
|
||||||
|
}
|
||||||
var unexcusedDelays = absenceProvider.absences
|
var unexcusedDelays = absenceProvider.absences
|
||||||
.where((a) => a.state == Justification.unexcused && a.delay > 0);
|
.where((a) => a.state == Justification.unexcused && a.delay > 0);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user