diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 326245c..20c1cd6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -31,6 +31,13 @@
+
+
+
+
+
+
+
diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb
index a97b098..3a60312 100644
--- a/lib/l10n/app_cs.arb
+++ b/lib/l10n/app_cs.arb
@@ -139,5 +139,7 @@
"createMessageNewPollOptionPlaceholder": "Nová možnost",
"createMessageErrorSelectRecipient": "Vyberte prosím příjemce",
"createMessageErrorNoMessage": "Napište prosím zprávu",
- "createMessageSend": "Odeslat"
+ "createMessageSend": "Odeslat",
+ "qrLoginPleaseLogin": "EduPage2 QR Přihlášení",
+ "qrLoginUseExistingCredentials": "Chystáte se přihlásit pomocí QR kódu"
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 46ad383..3e91a08 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -71,5 +71,7 @@
"createMessageNewPollOptionPlaceholder": "New option",
"createMessageErrorSelectRecipient": "Please select a recipient",
"createMessageErrorNoMessage": "Please write a message",
- "createMessageSend": "Send"
+ "createMessageSend": "Send",
+ "qrLoginPleaseLogin": "EduPage2 QR Login",
+ "qrLoginUseExistingCredentials": "You are about to login to EduPage2 using a QR code"
}
diff --git a/lib/main.dart b/lib/main.dart
index e4185fc..f44eeea 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,9 +1,13 @@
+import 'dart:async';
+
+import 'package:app_links/app_links.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:eduapge2/api.dart';
import 'package:eduapge2/homework.dart';
import 'package:eduapge2/icanteen.dart';
import 'package:eduapge2/load.dart';
import 'package:eduapge2/messages.dart';
+import 'package:eduapge2/qrlogin.dart';
import 'package:eduapge2/timetable.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
@@ -53,6 +57,8 @@ abstract class BaseState extends State {
}
}
+final GlobalKey navigatorKey = GlobalKey();
+
class MyApp extends StatelessWidget {
const MyApp({super.key});
@@ -78,6 +84,7 @@ class MyApp extends StatelessWidget {
return DynamicColorBuilder(builder: (lightColorScheme, darkColorScheme) {
return MaterialApp(
title: 'EduPage2',
+ navigatorKey: navigatorKey,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
navigatorObservers: [SentryNavigatorObserver(), observer],
@@ -107,6 +114,9 @@ class PageBaseState extends BaseState {
int _selectedIndex = 0;
String baseUrl = FirebaseRemoteConfig.instance.getString("testUrl");
+ late AppLinks _appLinks;
+ StreamSubscription? _linkSubscription;
+
bool loaded = false;
bool error = false; //for error status
@@ -125,6 +135,28 @@ class PageBaseState extends BaseState {
setOptimalDisplayMode();
if (!_isCheckingForUpdate) _checkForUpdate(); // ik that it's not necessary
super.initState();
+ initDeepLinks();
+ }
+
+ @override
+ void dispose() {
+ _linkSubscription?.cancel();
+ super.dispose();
+ }
+
+ Future initDeepLinks() async {
+ _appLinks = AppLinks();
+
+ _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
+ if (uri.path.startsWith('/l/')) {
+ final code = uri.pathSegments[1];
+ navigatorKey.currentState?.push(MaterialPageRoute(
+ builder: (context) => QRLoginPage(
+ code: code,
+ ),
+ ));
+ }
+ });
}
Future setOptimalDisplayMode() async {
diff --git a/lib/qrlogin.dart b/lib/qrlogin.dart
new file mode 100644
index 0000000..00bb01b
--- /dev/null
+++ b/lib/qrlogin.dart
@@ -0,0 +1,176 @@
+import 'package:dio/dio.dart';
+import 'package:eduapge2/main.dart';
+import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class QRLoginPage extends StatefulWidget {
+ final String code;
+ const QRLoginPage({super.key, required this.code});
+
+ @override
+ BaseState createState() => QRLoinPageState();
+}
+
+class QRLoinPageState extends BaseState {
+ AppLocalizations? local;
+ late SharedPreferences sharedPreferences;
+ String email = "";
+ String password = "";
+ String server = "";
+ bool _useCustomEndpoint = false;
+ String _customEndpoint = '';
+ bool showPassword = false;
+
+ @override
+ void initState() {
+ getPrefs();
+ super.initState();
+ }
+
+ @override
+ void setState(VoidCallback fn) {
+ if (!mounted) return;
+ super.setState(fn);
+ }
+
+ Future getPrefs() async {
+ sharedPreferences = await SharedPreferences.getInstance();
+ String? sEmail = sharedPreferences.getString("email");
+ String? sPassword = sharedPreferences.getString("password");
+ String? sServer = sharedPreferences.getString("server");
+ String? sEndpoint = sharedPreferences.getString("customEndpoint");
+
+ if (sEmail != null) {
+ email = sEmail;
+ }
+ if (sPassword != null) {
+ password = sPassword;
+ }
+ if (sServer != null) {
+ server = sServer;
+ }
+ if (sEndpoint != null && sEndpoint != "") {
+ _customEndpoint = sEndpoint;
+ _useCustomEndpoint = true;
+ }
+ setState(() {});
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ local ??= AppLocalizations.of(context);
+ return Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Card(
+ child: Padding(
+ padding: const EdgeInsets.all(15),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ local!.qrLoginPleaseLogin,
+ style: const TextStyle(
+ fontSize: 18,
+ ),
+ ),
+ Text(local!.qrLoginUseExistingCredentials),
+ TextField(
+ decoration: InputDecoration(
+ icon: const Icon(Icons.email),
+ hintText: local!.loginUsername,
+ ),
+ onChanged: (text) => {email = text},
+ keyboardType: TextInputType.emailAddress,
+ ),
+ TextField(
+ decoration: InputDecoration(
+ icon: const Icon(Icons.key),
+ hintText: local!.loginPassword,
+ suffixIcon: IconButton(
+ icon: Icon(showPassword
+ ? Icons.visibility
+ : Icons.visibility_off),
+ onPressed: () {
+ setState(() {
+ showPassword = !showPassword;
+ });
+ },
+ ),
+ ),
+ onChanged: (text) => {password = text},
+ obscureText: true,
+ keyboardType: TextInputType.visiblePassword,
+ ),
+ Row(
+ children: [
+ Checkbox(
+ value: _useCustomEndpoint,
+ onChanged: (value) {
+ setState(() {
+ _useCustomEndpoint = value!;
+ });
+ },
+ ),
+ Text(local!.loginCustomEndpointCheckbox),
+ ],
+ ),
+ if (_useCustomEndpoint)
+ TextField(
+ decoration: InputDecoration(
+ icon: const Icon(Icons.language),
+ hintText: local!.loginCustomEndpoint,
+ ),
+ onChanged: (value) => {_customEndpoint = value}),
+ TextField(
+ decoration: InputDecoration(
+ icon: const Icon(Icons.cloud_queue),
+ hintText: local!.loginServer,
+ ),
+ onChanged: (text) => {server = text},
+ keyboardType: TextInputType.url,
+ ),
+ const SizedBox(height: 10),
+ ElevatedButton(
+ onPressed: () async {
+ Dio dio = Dio();
+
+ var response = await dio.post(
+ 'https://ep2.vypal.me/qrlogin/${widget.code}',
+ options: Options(
+ headers: {
+ Headers.contentTypeHeader:
+ Headers.formUrlEncodedContentType,
+ },
+ ),
+ data: {
+ 'username': email,
+ 'password': password,
+ 'server': server,
+ 'endpoint': _customEndpoint,
+ },
+ );
+
+ if (response.statusCode == 200) {
+ navigatorKey.currentState!.pop();
+ } else {
+ // If the server did not return a 200 OK response, throw an exception.
+ throw Exception('Failed to load data');
+ }
+ },
+ style: ButtonStyle(
+ elevation: MaterialStateProperty.all(3),
+ ),
+ child: Text(local!.loginLogin),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index 2528735..aecfb5b 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include
+#include
#include
#include
@@ -14,6 +15,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
+ g_autoptr(FlPluginRegistrar) gtk_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
+ gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index b942fff..a98a528 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
+ gtk
sentry_flutter
url_launcher_linux
)
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 4216f5d..459ff28 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import app_links
import connectivity_plus
import dynamic_color
import firebase_analytics
@@ -18,6 +19,7 @@ import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index 741ac9e..cb61985 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.30"
+ app_links:
+ dependency: "direct main"
+ description:
+ name: app_links
+ sha256: "96e677810b83707ff5e10fac11e4839daa0ea4e0123c35864c092699165eb3db"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.1"
args:
dependency: transitive
description:
@@ -319,6 +327,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ gtk:
+ dependency: transitive
+ description:
+ name: gtk
+ sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
html_unescape:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 5ed8aa1..6a28649 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -31,6 +31,7 @@ dependencies:
flutter_displaymode: ^0.6.0
install_referrer: ^1.2.1
in_app_update: ^4.2.2
+ app_links: ^6.1.1
dev_dependencies:
integration_test:
sdk: flutter
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 1a60256..511a95b 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
+#include
#include
#include
#include
@@ -13,6 +14,8 @@
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ AppLinksPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DynamicColorPluginCApiRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 928840d..5995934 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ app_links
connectivity_plus
dynamic_color
firebase_core