diff --git a/.gitignore b/.gitignore index 8fc841f44e16865fc85cfcb1b65ca1507585a424..6d0602dc473f93f7d576824f245c1349447d32be 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ app.*.map.json /coverage .credentials* +devtools_options.yaml \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3a4974c1e1a15030e4db63e74feff6c071666e42..eb52a43f44f6f3440413613bcac7ec9950f29479 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ variables: # - "13.2" # - "13.2.1" # - "14.0.1" - flutterVersion: "3.13.6" + flutterVersion: "3.19.0" PKG_BUILD_FLAGS: "--no-obfuscate" stages: @@ -105,8 +105,10 @@ build:android: export PKG_BUILD_FLAGS="$PKG_BUILD_FLAGS --release" export PKG_RELEASE_TYPE="release" else - export PKG_BUILD_FLAGS="$PKG_BUILD_FLAGS --debug" - export PKG_RELEASE_TYPE="debug" + #export PKG_BUILD_FLAGS="$PKG_BUILD_FLAGS --debug" + #export PKG_RELEASE_TYPE="debug" + export PKG_BUILD_FLAGS="$PKG_BUILD_FLAGS --release" + export PKG_RELEASE_TYPE="release" fi export PKG_VERSION="`grep 'version: ' pubspec.yaml | sed -E 's/version: (.+)\+([0-9]+)/\1_\2/'`" export SEC_FILES="$CI_PROJECT_DIR/private_keys" @@ -129,6 +131,9 @@ build:android: - flutter pub get - flutter clean - flutter gen-l10n + # Following not required as expected, that all files are added to + # the repository. + #- flutter packages pub run build_runner build - flutter build appbundle $PKG_BUILD_FLAGS - mv build/app/outputs/bundle/${PKG_RELEASE_TYPE}/app-${PKG_RELEASE_TYPE}.aab build/app/outputs/bundle/flsvplan-android-${PKG_VERSION}-${PKG_RELEASE_TYPE}.aab || true - | @@ -227,6 +232,9 @@ build:ios: - flutter pub get - flutter clean - flutter gen-l10n + # Following not required as expected, that all files are added to + # the repository. + #- flutter packages pub run build_runner build - | echo "Unlock keychain" security unlock-keychain -p "$MATCH_KEYCHAIN_PASSWORD" "$MATCH_KEYCHAIN_NAME" @@ -256,8 +264,8 @@ build:ios: - | echo "Reset default keychain" security default-keychain -d user -s "login.keychain" - echo "Delete keychain again" - security delete-keychain "$MATCH_KEYCHAIN_NAME" + #echo "Delete keychain again" + #security delete-keychain "$MATCH_KEYCHAIN_NAME" echo "Delete downloaded mobile provision profiles" rm -Rf "$HOME/Library/MobileDevice/Provisioning Profiles/*.mobileprovision" tags: diff --git a/android/app/build.gradle b/android/app/build.gradle index 49d3535241942d064e17e6fc29beacf2afb8768b..70c8e9af6b1276b00ad466694569713e3c3d8bb0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,10 @@ + +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') def keystoreProperties = new Properties() @@ -13,11 +20,6 @@ if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -28,13 +30,11 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { compileSdkVersion 34 //flutter.compileSdkVersion // which value is it? ndkVersion flutter.ndkVersion + //ndkVersion "25.1.8937393" + namespace "de.fls_wiesbaden.vplan" compileOptions { // Flag to enable support for the new language APIs @@ -57,7 +57,9 @@ android { applicationId "de.fls_wiesbaden.vplan" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 20 + // Updated due to bug https://github.com/juliansteenbakker/mobile_scanner/issues/729 + //minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -83,10 +85,16 @@ android { signingConfig signingConfigs.debug } matchingFallbacks = ['release'] + ndk { + abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + } } debug { matchingFallbacks = ['debug'] signingConfig signingConfigs.debug + ndk { + abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + } } } } @@ -96,9 +104,9 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + //implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.window:window:1.0.0' implementation 'androidx.window:window-java:1.0.0' implementation('com.github.UnifiedPush:android-foss_embedded_fcm_distributor:1.0.0-beta3') - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' } diff --git a/android/build.gradle b/android/build.gradle index e7b1f12e957ebacdec710d2fa5b26d1a3bb84bc4..7824f9d412714ad11a455d8f6cc6a9b27bdc17db 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.8.22' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf06ae649ea809590f8a861059886502e8..4ae3a020bab99188a4cd1930ba4451e9f0e849f9 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,26 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.4.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.0" apply false +} + +include ":app" diff --git a/lib/controllers/authcontroller.dart b/lib/controllers/authcontroller.dart index 54439c6719a4c512941a554fad8c4da19a2061d7..a52e4deeaaaefe845b124fdbf0b45ee14706adc8 100644 --- a/lib/controllers/authcontroller.dart +++ b/lib/controllers/authcontroller.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; +import 'package:de_fls_wiesbaden_vplan/ui/helper/exceptions.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:de_fls_wiesbaden_vplan/ui/helper/consts.dart'; @@ -39,10 +40,10 @@ class AuthController extends ChangeNotifier { final log = Logger(vplanLoggerId); final oidc = await http.get(Uri.parse(await oidcEndpoint())).onError((error, stackTrace) { log.warning("Could not determine oidc config: ${error.toString()}.", error, stackTrace); - return Future.error("Could not determine OIDC config!"); + return Future.error(ApiConnectException("Could not determine OIDC config!")); }); if (oidc.statusCode != 200) { - return Future.error("Could not determine OIDC config!"); + return Future.error(ApiConnectException("Could not determine OIDC config!")); } Map oidcConfig = jsonDecode(oidc.body); return oidcConfig['authorization_endpoint']; @@ -90,13 +91,13 @@ class AuthController extends ChangeNotifier { encoding: Encoding.getByName('utf-8'), ).onError((error, stackTrace) { log.warning("Login not possible - could not connect: ${error.toString()}.", error, stackTrace); - throw Exception("Could not connect!"); + throw ApiConnectException("Could not connect!"); }); if (response.statusCode != 200) { return false; } await config.setAuthJwt(response.body); - await this.updatePermissions(); + await updatePermissions(); if (notify) { notifyListeners(); } @@ -199,7 +200,7 @@ class AuthController extends ChangeNotifier { return false; } await config.setAuthJwt(response.body); - await this.updatePermissions(); + await updatePermissions(); return true; } @@ -216,12 +217,10 @@ class AuthController extends ChangeNotifier { log.info("No valid token - no permissions!"); config.setTeacherPermission(false); } else { - Map decodedToken = JwtDecoder.decode((await this.getAccessToken())!); + Map decodedToken = JwtDecoder.decode((await getAccessToken())!); try { await config.setTeacherPermission(decodedToken['scopes'].indexOf('vplan-teacher') != -1); - log.info('User' + - ((decodedToken['scopes'].indexOf('vplan-teacher') != -1) ? '' : ' does not') + - ' has teacher permission' + log.info('User${(decodedToken['scopes'].indexOf('vplan-teacher') != -1) ? '' : ' does not'} has teacher permission' ); } on Exception catch (error, stackTrace) { log.warning("Could not verify token scopes. ", error, stackTrace); diff --git a/lib/guards/auth.guards.dart b/lib/guards/auth.guards.dart new file mode 100644 index 0000000000000000000000000000000000000000..9e80ebb3094a88d7571434d78213fea1cbb76dac --- /dev/null +++ b/lib/guards/auth.guards.dart @@ -0,0 +1,20 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:de_fls_wiesbaden_vplan/controllers/authcontroller.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; + +class AuthGuard extends AutoRouteGuard { + @override + void onNavigation(NavigationResolver resolver, StackRouter router) async { + AuthController authController = AuthController.getInstance(); + if (await authController.isLoggedIn()) { + resolver.next(true); + } else { + router.push(AuthUiRoute()); + /* onResult: (didLogin) { + resolver.next(didLogin); + }, + ));*/ + } + } + +} \ No newline at end of file diff --git a/lib/guards/firstrun.guards.dart b/lib/guards/firstrun.guards.dart new file mode 100644 index 0000000000000000000000000000000000000000..a971b53def695783646115d2ff77a4773571381b --- /dev/null +++ b/lib/guards/firstrun.guards.dart @@ -0,0 +1,15 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; +import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; + +class FirstRunGuard extends AutoRouteGuard { + @override + void onNavigation(NavigationResolver resolver, StackRouter router) async { + final Config config = Config.getInstance(); + if (await config.isFirstCall()) { + resolver.next(true); + } else { + router.push(const FlsVplanMainUiRoute()); + } + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 36d19091c1b17cea3123df89edcbbfaf007a5186..97b445cbf648167ee0f51640bf67fbca9125d6f3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,8 @@ import 'dart:io'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.dart'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/storage/planstorage.dart'; -import 'package:de_fls_wiesbaden_vplan/ui/authui.dart'; import 'package:de_fls_wiesbaden_vplan/ui/helper/consts.dart'; import 'package:de_fls_wiesbaden_vplan/ui/styles/plancolors.dart'; import 'package:de_fls_wiesbaden_vplan/utils/logger.dart'; @@ -136,9 +136,9 @@ class FlsVplanApp extends StatefulWidget { const FlsVplanApp( //this.notificationAppLaunchDetails, { - Key? key, + super.key, } - ) : super(key: key); + ); //final NotificationAppLaunchDetails? notificationAppLaunchDetails; @@ -151,15 +151,22 @@ class FlsVplanApp extends StatefulWidget { class _FlsVplanAppState extends State { + final _appRouter = AppRouter(); + bool _notificationsEnabled = false; @override void initState() { super.initState(); + final log = getVPlanLogger(); BackgroundPush.initialize(); _isAndroidPermissionGranted(); - _requestPermissions(); + try { + _requestPermissions(); + } catch(e) { + log.severe("Got exception on requesting permission: ${e.toString()}"); + } } Future _isAndroidPermissionGranted() async { @@ -210,7 +217,7 @@ class _FlsVplanAppState extends State { flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); - granted = await androidImplementation?.requestPermission(); + granted = await androidImplementation?.requestNotificationsPermission(); } else { log.info("Notifications disabled as not supported."); } @@ -230,7 +237,7 @@ class _FlsVplanAppState extends State { final log = getVPlanLogger(); log.finest("Building FlsVplanApp"); - return MaterialApp( + return MaterialApp.router( debugShowCheckedModeBanner: false, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, @@ -238,7 +245,7 @@ class _FlsVplanAppState extends State { theme: ThemeData( primarySwatch: PlanColors.MatPrimaryTextColor, ), - home: const AuthUi(), + routerConfig: _appRouter.config() ); } } diff --git a/lib/models/bookmarkable.dart b/lib/models/bookmarkable.dart index 09e3dc1a1fd05df8e50eff44106d4803832356a5..09c0c309376456144d93fb889f11ed6d520bfa2a 100644 --- a/lib/models/bookmarkable.dart +++ b/lib/models/bookmarkable.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; class Bookmarkable extends ChangeNotifier { bool bookmarked = false; - void setBookmarked(bool bookmarked) { + void setBookmarked(bool bookmarked, {bool skipNotification = false}) { this.bookmarked = bookmarked; - notifyListeners(); + if (!skipNotification) { + notifyListeners(); + } } void toggleBookmarked() => setBookmarked(!bookmarked); bool isBookmarked() => bookmarked; diff --git a/lib/models/entry.dart b/lib/models/entry.dart index 90952ce4f4dbf4c79ddda7b79f0cbb658cf37fc5..687aca026b6a11ddd6c1e0b8d6a5255e06490228 100644 --- a/lib/models/entry.dart +++ b/lib/models/entry.dart @@ -73,7 +73,7 @@ class Entry implements Comparable { } bool isChange() { - return !this.isRegular() && !this.isFree(); + return !isRegular() && !isFree(); } bool isYardDuty() { @@ -81,15 +81,15 @@ class Entry implements Comparable { } bool isRoomChanged() { - return this.isChange() && (entryType & 2) == 2; + return isChange() && (entryType & 2) == 2; } bool isTeacherChanged() { - return this.isChange() && (entryType & 4) == 4; + return isChange() && (entryType & 4) == 4; } bool isSubjectChanged() { - return this.isChange() && (entryType & 8) == 8; + return isChange() && (entryType & 8) == 8; } String? get chgNotes { diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart new file mode 100644 index 0000000000000000000000000000000000000000..d513b5a0156dd99cf4f4ae5526eb3457dd0e2a40 --- /dev/null +++ b/lib/routes/routes.dart @@ -0,0 +1,36 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:de_fls_wiesbaden_vplan/guards/auth.guards.dart'; +import 'package:de_fls_wiesbaden_vplan/guards/firstrun.guards.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; + +@AutoRouterConfig(replaceInRouteName: 'Route') +class AppRouter extends $AppRouter { + @override + RouteType get defaultRouteType => + const RouteType.material(); //.cupertino, .adaptive ..etc + + @override + List get routes => [ + AutoRoute( + page: FlsVplanMainUiRoute.page, path: '/', guards: [AuthGuard()]), + AutoRoute( + page: WizardRoute.page, + path: '/wizard', + guards: [AuthGuard(), FirstRunGuard()] + //transitionsBuilder: TransitionsBuilders.zoomIn, + ), + AutoRoute(page: AboutUiRoute.page, path: '/about'), + AutoRoute( + page: AuthUiRoute.page, + path: '/login', + //keepHistory: false, + //transitionsBuilder: TransitionsBuilders.slideLeftWithFade, + ), + AutoRoute( + page: AuthScanUiRoute.page, + path: '/login/scan', + //keepHistory: false + //transitionsBuilder: TransitionsBuilders.slideLeftWithFade, + ) + ]; +} diff --git a/lib/routes/routes.gr.dart b/lib/routes/routes.gr.dart new file mode 100644 index 0000000000000000000000000000000000000000..04d643065c9e631d116aaebeea386905cc4af6f2 --- /dev/null +++ b/lib/routes/routes.gr.dart @@ -0,0 +1,184 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:auto_route/auto_route.dart' as _i6; +import 'package:de_fls_wiesbaden_vplan/ui/aboutui.dart' as _i1; +import 'package:de_fls_wiesbaden_vplan/ui/authscanui.dart' as _i2; +import 'package:de_fls_wiesbaden_vplan/ui/authui.dart' as _i3; +import 'package:de_fls_wiesbaden_vplan/ui/flsvplanmainui.dart' as _i4; +import 'package:de_fls_wiesbaden_vplan/ui/helper/types.dart' as _i8; +import 'package:de_fls_wiesbaden_vplan/ui/wizard.dart' as _i5; +import 'package:flutter/material.dart' as _i7; + +abstract class $AppRouter extends _i6.RootStackRouter { + $AppRouter({super.navigatorKey}); + + @override + final Map pagesMap = { + AboutUiRoute.name: (routeData) { + return _i6.AutoRoutePage( + routeData: routeData, + child: const _i1.AboutUi(), + ); + }, + AuthScanUiRoute.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const AuthScanUiRouteArgs()); + return _i6.AutoRoutePage( + routeData: routeData, + child: _i2.AuthScanUi( + key: args.key, + onScanCompleted: args.onScanCompleted, + ), + ); + }, + AuthUiRoute.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const AuthUiRouteArgs()); + return _i6.AutoRoutePage( + routeData: routeData, + child: _i3.AuthUi( + key: args.key, + errorMessage: args.errorMessage, + ), + ); + }, + FlsVplanMainUiRoute.name: (routeData) { + return _i6.AutoRoutePage( + routeData: routeData, + child: const _i4.FlsVplanMainUi(), + ); + }, + WizardRoute.name: (routeData) { + return _i6.AutoRoutePage( + routeData: routeData, + child: const _i5.Wizard(), + ); + }, + }; +} + +/// generated route for +/// [_i1.AboutUi] +class AboutUiRoute extends _i6.PageRouteInfo { + const AboutUiRoute({List<_i6.PageRouteInfo>? children}) + : super( + AboutUiRoute.name, + initialChildren: children, + ); + + static const String name = 'AboutUiRoute'; + + static const _i6.PageInfo page = _i6.PageInfo(name); +} + +/// generated route for +/// [_i2.AuthScanUi] +class AuthScanUiRoute extends _i6.PageRouteInfo { + AuthScanUiRoute({ + _i7.Key? key, + void Function(_i8.AuthLoginResult)? onScanCompleted, + List<_i6.PageRouteInfo>? children, + }) : super( + AuthScanUiRoute.name, + args: AuthScanUiRouteArgs( + key: key, + onScanCompleted: onScanCompleted, + ), + initialChildren: children, + ); + + static const String name = 'AuthScanUiRoute'; + + static const _i6.PageInfo page = + _i6.PageInfo(name); +} + +class AuthScanUiRouteArgs { + const AuthScanUiRouteArgs({ + this.key, + this.onScanCompleted, + }); + + final _i7.Key? key; + + final void Function(_i8.AuthLoginResult)? onScanCompleted; + + @override + String toString() { + return 'AuthScanUiRouteArgs{key: $key, onScanCompleted: $onScanCompleted}'; + } +} + +/// generated route for +/// [_i3.AuthUi] +class AuthUiRoute extends _i6.PageRouteInfo { + AuthUiRoute({ + _i7.Key? key, + String? errorMessage, + List<_i6.PageRouteInfo>? children, + }) : super( + AuthUiRoute.name, + args: AuthUiRouteArgs( + key: key, + errorMessage: errorMessage, + ), + initialChildren: children, + ); + + static const String name = 'AuthUiRoute'; + + static const _i6.PageInfo page = + _i6.PageInfo(name); +} + +class AuthUiRouteArgs { + const AuthUiRouteArgs({ + this.key, + this.errorMessage, + }); + + final _i7.Key? key; + + final String? errorMessage; + + @override + String toString() { + return 'AuthUiRouteArgs{key: $key, errorMessage: $errorMessage}'; + } +} + +/// generated route for +/// [_i4.FlsVplanMainUi] +class FlsVplanMainUiRoute extends _i6.PageRouteInfo { + const FlsVplanMainUiRoute({List<_i6.PageRouteInfo>? children}) + : super( + FlsVplanMainUiRoute.name, + initialChildren: children, + ); + + static const String name = 'FlsVplanMainUiRoute'; + + static const _i6.PageInfo page = _i6.PageInfo(name); +} + +/// generated route for +/// [_i5.Wizard] +class WizardRoute extends _i6.PageRouteInfo { + const WizardRoute({List<_i6.PageRouteInfo>? children}) + : super( + WizardRoute.name, + initialChildren: children, + ); + + static const String name = 'WizardRoute'; + + static const _i6.PageInfo page = _i6.PageInfo(name); +} diff --git a/lib/storage/config.dart b/lib/storage/config.dart index 714be92072f027e2fdabe4ef8a18efb0c2027e58..b8f6a462191bc3450d95b98e62681aa597833f63 100644 --- a/lib/storage/config.dart +++ b/lib/storage/config.dart @@ -154,9 +154,9 @@ class Config extends ChangeNotifier { } /// Update configuration and set plan type. - void setMode(PlanType pt) async { + Future setMode(PlanType pt) async { _planType = pt; - await _storage + return _storage .write( key: configKeyMode, value: pt == PlanType.teacher ? configPlanTeacher : configPlanPupil) @@ -164,7 +164,7 @@ class Config extends ChangeNotifier { } /// Update configuration and set plan type. - void setModeString(String planType) async { + Future setModeString(String planType) async { setMode( planType == configPlanTeacher ? PlanType.teacher : PlanType.pupil); } @@ -190,7 +190,7 @@ class Config extends ChangeNotifier { if (await _storage.containsKey(key: configKeyAddRegularPlan)) { return (await _storage.read(key: configKeyAddRegularPlan))! == 'true'; } else { - return false; + return true; } } @@ -233,7 +233,7 @@ class Config extends ChangeNotifier { } /// Set school identifier. - void setSchool(String school) async { + Future setSchool(String school) async { // Throws an exception, if school cannot be found. if (Config.schools.indexWhere((element) => element.id == school) < 0) { throw SchoolNotFoundException("School $school not found!"); @@ -242,7 +242,7 @@ class Config extends ChangeNotifier { BackgroundPush.unregister(_school.notifyInstance); } _schoolName = school; - await _storage + return _storage .write(key: configKeySchool, value: school) .whenComplete(() => notifyListeners()); } @@ -299,17 +299,21 @@ class Config extends ChangeNotifier { } Future getTeacherPermission() async { - if (await _storage.containsKey(key: configKeyPermTeacher)) { - return (await _storage.read(key: configKeyPermTeacher))! == 'true'; - } else { - return false; + try { + if (await _storage.containsKey(key: configKeyPermTeacher)) { + return (await _storage.read(key: configKeyPermTeacher))! == 'true'; + } + } catch (e) { + final log = Logger(vplanLoggerId); + log.severe("Config: Failed loading teacher permission -- fallback to false!"); } + return false; } Future setTeacherPermission(bool hasPerm) async { _teacherPermission = hasPerm; if (!hasPerm) { - this.setMode(PlanType.pupil); + setMode(PlanType.pupil); } await _storage.write(key: configKeyPermTeacher, value: hasPerm.toString()); } diff --git a/lib/storage/planstorage.dart b/lib/storage/planstorage.dart index 6cfec8fa61eedee5886d6d6a4dce1f95734e2089..053d962245d9db1663a94c8534afbb78c83a9cc1 100644 --- a/lib/storage/planstorage.dart +++ b/lib/storage/planstorage.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:de_fls_wiesbaden_vplan/ui/helper/apirequest.dart'; @@ -15,6 +16,7 @@ import 'package:de_fls_wiesbaden_vplan/storage/schoolclassstorage.dart'; import 'package:de_fls_wiesbaden_vplan/storage/teacherstorage.dart'; import 'package:de_fls_wiesbaden_vplan/ui/helper/consts.dart'; import 'package:logging/logging.dart'; + int notificationId = 0; class PlanStorage extends ChangeNotifier { @@ -25,9 +27,10 @@ class PlanStorage extends ChangeNotifier { String? _etag; static const collectionName = "plan"; final LocalStorage storage = LocalStorage(collectionName); - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); var _loading = false; - Future? _depRefresh; + Timer? _depRefresh; Plan? get plan => _plan; final SchoolClassStorage _scs = SchoolClassStorage(); @@ -45,22 +48,29 @@ class PlanStorage extends ChangeNotifier { void _depDataChanged() { final log = Logger(vplanLoggerId); - log.fine("Some dependencies changed. Re-schedule personal plan reload (${_depRefresh != null ? "Already set" : "Not set"})."); - _depRefresh ??= Future.delayed(const Duration(milliseconds: 500), () { - final log = Logger(vplanLoggerId); - log.fine("Future for _depDataChanged started."); - if (_plan != null) { - _personalPlan = Plan.copyFilter( - _plan!, bookmarked: _scs.getBookmarked(), bookmarkedLessons: _scs.getBookmarkedLessonsHash(), bookmarkedTeachers: _tcs.getBookmarked() - ); - log.info("Personal plan re-generated."); - notifyListeners(); - } - }).whenComplete(() => _depRefresh = null); + bool alreadyActive = _depRefresh?.isActive ?? false; + log.fine( + "Some dependencies changed. Re-schedule personal plan reload (${alreadyActive ? "Already set" : "Not set"})."); + _depRefresh?.cancel(); + _depRefresh = Timer(const Duration(seconds: 2), () { + final log = Logger(vplanLoggerId); + log.fine("Future for _depDataChanged started."); + if (_plan != null) { + _personalPlan = Plan.copyFilter(_plan!, + bookmarked: _scs.getBookmarked(), + bookmarkedLessons: _scs.getBookmarkedLessonsHash(), + bookmarkedTeachers: _tcs.getBookmarked()); + log.info("Personal plan re-generated."); + notifyListeners(); + } + _depRefresh = null; + }); } void handlePlanModeChange() { if (Config.getInstance().mode != planType) { + final log = Logger(vplanLoggerId); + log.fine("handlePlanModeChange: Plan mode changed old: $planType, new: ${Config.getInstance().mode}."); planType = Config.getInstance().mode; // check what we need to disable. if (planType == PlanType.pupil) { @@ -68,6 +78,7 @@ class PlanStorage extends ChangeNotifier { } else { _scs.disableBookmarks(); } + notifyListeners(); } } @@ -91,7 +102,8 @@ class PlanStorage extends ChangeNotifier { if (numberEntries > 1) { body = "Es liegen $numberEntries neue Vertretungsplaneinträge vor."; } else if (numberEntries > 0) { - body = "Es gibt einen neuen Vertretungsplaneintrag für den ${newEntries.first.getShortDateString()}."; + body = + "Es gibt einen neuen Vertretungsplaneintrag für den ${newEntries.first.getShortDateString()}."; } else if (newLessons > 0) { body = "Es sind neue Klassen/Kurse verfügbar!"; } else { @@ -100,26 +112,26 @@ class PlanStorage extends ChangeNotifier { AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - vplanNewEntriesChannelId, - vplanNewEntriesChannelName, - channelDescription: vplanNewEntriesChannelDescription, - importance: Importance.defaultImportance, - priority: Priority.defaultPriority, - ticker: 'ticker', - number: numberEntries - ); + vplanNewEntriesChannelId, vplanNewEntriesChannelName, + channelDescription: vplanNewEntriesChannelDescription, + importance: Importance.defaultImportance, + priority: Priority.defaultPriority, + ticker: 'ticker', + number: numberEntries); NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin.show( - notificationId++, 'Neue Vertretungsplaneinträge', body, notificationDetails); + await flutterLocalNotificationsPlugin.show(notificationId++, + 'Neue Vertretungsplaneinträge', body, notificationDetails); } void setPlan( - {required Plan plan, required DateTime fetched, bool savePlan = false, bool wasRefresh = false}) { - + {required Plan plan, + required DateTime fetched, + bool savePlan = false, + bool wasRefresh = false}) { final log = Logger(vplanLoggerId); int newLessons = 0; - + // is it required to update the lessons? if (savePlan) { for (var elem in plan.getEntries()) { @@ -140,7 +152,10 @@ class PlanStorage extends ChangeNotifier { Plan prevPersonalPlan = _personalPlan ?? Plan(list: [], lastUpdate: null); _plan = plan; _standinPlan = Plan.copyFilter(plan, onlyStandin: true); - _personalPlan = Plan.copyFilter(plan, bookmarked: _scs.getBookmarked(), bookmarkedLessons: _scs.getBookmarkedLessonsHash(), bookmarkedTeachers: _tcs.getBookmarked()); + _personalPlan = Plan.copyFilter(plan, + bookmarked: _scs.getBookmarked(), + bookmarkedLessons: _scs.getBookmarkedLessonsHash(), + bookmarkedTeachers: _tcs.getBookmarked()); _fetched = fetched; _loading = false; notifyListeners(); @@ -165,8 +180,11 @@ class PlanStorage extends ChangeNotifier { SchoolClassStorage get schoolClassStorage => _scs; TeacherStorage get teacherStorage => _tcs; - Map toJson() => - {'plan': _plan?.toJson(), 'fetched': _fetched?.toIso8601String(), 'etag': _etag}; + Map toJson() => { + 'plan': _plan?.toJson(), + 'fetched': _fetched?.toIso8601String(), + 'etag': _etag + }; void save() { final log = Logger(vplanLoggerId); @@ -196,7 +214,7 @@ class PlanStorage extends ChangeNotifier { final bool storageReady = await storage.ready; log.fine("Plan::load: Storage is ${storageReady ? "ready" : "not ready"}."); final Map? storageData = storage.getItem("data"); - + if (!refresh && _plan != null) { log.info("Plan::load: Load plan from cache."); return getPlan(personalPlan: personalPlan)!; @@ -209,7 +227,11 @@ class PlanStorage extends ChangeNotifier { } else { return downloadPlan().then((value) { if (value != null) { - setPlan(fetched: DateTime.now(), plan: value, savePlan: true, wasRefresh: refresh); + setPlan( + fetched: DateTime.now(), + plan: value, + savePlan: true, + wasRefresh: refresh); } log.info("Plan::load: Just downloaded plan and use that."); return getPlan(personalPlan: personalPlan)!; @@ -217,7 +239,6 @@ class PlanStorage extends ChangeNotifier { log.severe("Download and parsing of plan failed", error, stackTrace); return Future.error(error!, stackTrace); }); - } } @@ -230,8 +251,10 @@ class PlanStorage extends ChangeNotifier { 'regular': Config.getInstance().addRegularPlan ? '1' : '0', 'filterElapsedHours': '0', 'days': Config.getInstance().numberDays.toString(), - 'planMode': Config.getInstance().mode == PlanType.teacher ? 'teacher' : 'pupil', - 'view': Config.getInstance().mode == PlanType.teacher ? 'teacher' : 'pupil' + 'planMode': + Config.getInstance().mode == PlanType.teacher ? 'teacher' : 'pupil', + 'view': + Config.getInstance().mode == PlanType.teacher ? 'teacher' : 'pupil' }; if (Config.getInstance().notifyRegistered) { queryParameters['up'] = Config.getInstance().notifyEndpoint; @@ -242,33 +265,38 @@ class PlanStorage extends ChangeNotifier { } if (_fetched != null) { final DateFormat formatter = DateFormat('EEE, dd LLL y H:m:s'); - headers[HttpHeaders.ifModifiedSinceHeader] = "${formatter.format(_fetched!.toUtc())} GMT"; + headers[HttpHeaders.ifModifiedSinceHeader] = + "${formatter.format(_fetched!.toUtc())} GMT"; } - final response = await defaultApiRequest( - "/vplan/loadPlan", - headers: headers, - queryParameters: queryParameters - ).onError((error, stackTrace) { + final response = await defaultApiRequest("/vplan/loadPlan", + headers: headers, queryParameters: queryParameters) + .onError((error, stackTrace) { return Future.error(error!, stackTrace); }); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. - var rawPlan = jsonDecode(await response.stream.bytesToString()) as Map; + var rawPlan = jsonDecode(await response.stream.bytesToString()) + as Map; developer.log("VPlan downloaded -- status: ${response.statusCode}"); // We should set the etag. - String? etag = response.headers.containsKey(HttpHeaders.etagHeader) ? response.headers[HttpHeaders.etagHeader] : null; + String? etag = response.headers.containsKey(HttpHeaders.etagHeader) + ? response.headers[HttpHeaders.etagHeader] + : null; if (etag != null) { _etag = etag; } - log.info("Downloaded vplan -- status: ${response.statusCode}, etag: ${etag ?? "-"}"); + log.info( + "Downloaded vplan -- status: ${response.statusCode}, etag: ${etag ?? "-"}"); return Plan.fromJsonUpstream(rawPlan); } else if (response.statusCode == 304 && _plan != null) { - log.info("Downloaded vplan - no data changed! -- status: ${response.statusCode}"); + log.info( + "Downloaded vplan - no data changed! -- status: ${response.statusCode}"); return null; } else if (response.statusCode == 204 || response.statusCode == 304) { - log.info("Downloaded vplan - no data changed or returned! -- status: ${response.statusCode}"); + log.info( + "Downloaded vplan - no data changed or returned! -- status: ${response.statusCode}"); return Plan(list: [], lastUpdate: DateTime.now()); } else { log.warning("Vplan download failed -- status: ${response.statusCode}"); diff --git a/lib/storage/schoolclassstorage.dart b/lib/storage/schoolclassstorage.dart index c31925e5b95f058317c0646ab1125347b254e787..a504735cb6c21586b74aaac8ecae979b244a8725 100644 --- a/lib/storage/schoolclassstorage.dart +++ b/lib/storage/schoolclassstorage.dart @@ -40,21 +40,23 @@ class SchoolClassStorage extends ChangeNotifier { /// Disable all pupil related bookmarks. void disableBookmarks() { + final log = Logger(vplanLoggerId); + log.fine("Clear pupil mode bookmarks."); for (var item in _types.values) { if (item.isBookmarked()) { - item.setBookmarked(false); + item.setBookmarked(false, skipNotification: true); } } for(var item in _lessons) { if (item.isBookmarked()) { - item.setBookmarked(false); + item.setBookmarked(false, skipNotification: true); } } for(var item in _list.values) { if (item.isBookmarked()) { - item.setBookmarked(false); + item.setBookmarked(false, skipNotification: true); } } } diff --git a/lib/storage/teacherstorage.dart b/lib/storage/teacherstorage.dart index de02f04acda448d70346fa659582d63caa281707..80f37cb81c71feff414724df8bb16a5f37991163 100644 --- a/lib/storage/teacherstorage.dart +++ b/lib/storage/teacherstorage.dart @@ -31,9 +31,11 @@ class TeacherStorage extends ChangeNotifier { } void disableBookmarks() { + final log = Logger(vplanLoggerId); + log.fine("Clear teacher bookmarks."); for(var item in _list) { if (item.isBookmarked()) { - item.setBookmarked(false); + item.setBookmarked(false, skipNotification: true); } } } diff --git a/lib/ui/aboutui.dart b/lib/ui/aboutui.dart index c061417393713ce54f425442549ba14ac15c572e..43fd28566ec0b8966997e0c14242bc84a3e1664a 100644 --- a/lib/ui/aboutui.dart +++ b/lib/ui/aboutui.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:de_fls_wiesbaden_vplan/ui/styles/plancolors.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/gestures.dart'; @@ -10,6 +11,7 @@ import 'package:url_launcher/url_launcher.dart'; /// containing information about the app, /// publisher, producer, developers, /// and licenses. +@RoutePage() class AboutUi extends StatelessWidget { const AboutUi({super.key}); diff --git a/lib/ui/authscanui.dart b/lib/ui/authscanui.dart index 9c8e8ade58ca4e687207e49e272dbeb02e7dba37..5e929f1a9b82bde7712e9203998c5493847f2878 100644 --- a/lib/ui/authscanui.dart +++ b/lib/ui/authscanui.dart @@ -1,56 +1,132 @@ +import 'dart:convert'; + +import 'package:auto_route/auto_route.dart'; +import 'package:de_fls_wiesbaden_vplan/controllers/authcontroller.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; +import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/ui/helper/consts.dart'; +import 'package:de_fls_wiesbaden_vplan/ui/helper/exceptions.dart'; +import 'package:de_fls_wiesbaden_vplan/ui/helper/types.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; -/// Provides widget to scan some kind of -/// login card for authentication. +/// Provides widget to scan some kind of +/// login card for authentication. /// This makes it more easy to login. /// Content is not validated within this widget. /// Its valided in the request widget. +@RoutePage() class AuthScanUi extends StatefulWidget { - const AuthScanUi({super.key}); + const AuthScanUi({super.key, this.onScanCompleted}); + final void Function(AuthLoginResult)? onScanCompleted; @override State createState() => _AuthScanUi(); } class _AuthScanUi extends State { - final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); - Barcode? result; - QRViewController? controller; - String? scanResult; + final AuthController _authController = AuthController.getInstance(); + ValueNotifier loginOngoing = ValueNotifier(false); @override Widget build(BuildContext context) { final log = Logger(vplanLoggerId); - void onQRViewCreated(QRViewController controller) { - this.controller = controller; - controller.scannedDataStream.listen((scanData) { - setState(() { - result = scanData; - scanResult = scanData.code; - }); - log.finest("Scanned barcode: $scanResult"); - Navigator.pop(context, scanResult); - }); - } - return Scaffold( appBar: AppBar(title: Text(AppLocalizations.of(context)!.loginLoginCard)), - body: QRView( - key: qrKey, - onQRViewCreated: onQRViewCreated, - ) + body: ListenableBuilder( + listenable: loginOngoing, + builder: (context, child) { + if (loginOngoing.value) { + return const Center( + child: SizedBox( + height: 80, + width: 80, + child: CircularProgressIndicator())); + } else { + return MobileScanner( + onDetect: (capture) async { + final List barcodes = capture.barcodes; + setState(() => loginOngoing.value = true); + if (barcodes.isNotEmpty) { + String? scanResult = barcodes.first.rawValue; + if (scanResult != null) { + String? scanResultReadable = scanResult.replaceAll( + RegExp(r'"clientSecret": +".*"'), + '"clientSecret": "***"'); + log.finest("Scanned barcode: $scanResultReadable"); + await _checkLoginCardResult(scanResult); + } else { + log.finest("No barcode scanned."); + sendResponse(AuthLoginResult('cancelled')); + } + } + }, + ); + } + }), ); - + } + + Future _checkLoginCardResult(String answer) async { + final log = Logger(vplanLoggerId); + final Config config = Config.getInstance(); + bool loginOk = false; + String? errorType; + try { + final object = jsonDecode(answer); + List> vl = []; + vl.add(config.setAuthUser(object['clientId'])); + vl.add(config.setAuthSecret(object['clientSecret'])); + vl.add(config.setSchool(object['school'])); + if (object['mode'] != null) { + vl.add(config.setModeString(object['mode'])); + } + await Future.wait(vl); + + loginOk = await _authController + .login() + .timeout(const Duration(seconds: 2), onTimeout: () { + errorType = 'no-internet'; + return false; + }).catchError((e) { + if (e.runtimeType == ApiConnectException) { + errorType = 'no-internet'; + } else { + errorType = 'invalid-barcode'; + } + return false; + }); + log.finest( + "Login triggered and got result: ${loginOk ? "Perfect!" : "Failed!"}"); + } on SchoolNotFoundException { + errorType = 'school-not-supported'; + } on Exception { + errorType = 'invalid-barocde'; + } + + if (mounted && context.mounted) { + if (loginOk) { + context.navigateTo(const WizardRoute()); + } else if (errorType != null) { + sendResponse(AuthLoginResult(errorType!)); + } + } + } + + void sendResponse(AuthLoginResult message) { + if (context.mounted) { + if (widget.onScanCompleted != null) { + widget.onScanCompleted!(message); + } + context.popRoute(); + } } @override void dispose() { - controller?.dispose(); super.dispose(); } -} \ No newline at end of file +} diff --git a/lib/ui/authui.dart b/lib/ui/authui.dart index 1169c9c73fa2c0621272f622151ac1dcf77c9a3b..9c02c5e49fdaf11558c9b730e7a3cef5fc897381 100644 --- a/lib/ui/authui.dart +++ b/lib/ui/authui.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:auto_route/auto_route.dart'; import 'package:de_fls_wiesbaden_vplan/controllers/authcontroller.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/ui/aboutui.dart'; import 'package:de_fls_wiesbaden_vplan/ui/authscanui.dart'; @@ -11,10 +13,11 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; +@RoutePage() class AuthUi extends StatefulWidget { const AuthUi({super.key, this.errorMessage}); - /// From extern given error message - e.g. + /// From extern given error message - e.g. /// tokens expired and automatic refreshing /// is not possible. final String? errorMessage; @@ -27,9 +30,9 @@ class _AuthUi extends State { final GlobalKey _formKey = GlobalKey(); final AuthController _authController = AuthController.getInstance(); ValueNotifier loginOngoing = ValueNotifier(false); - ValueNotifier isLoggedIn = ValueNotifier(false); TextEditingController userName = TextEditingController(); TextEditingController loginSecret = TextEditingController(); + /// external as well as internal error message. /// External: e.g. token is invalid and cannot be refreshed. /// Internal: e.g. username / password is wrong. @@ -38,7 +41,6 @@ class _AuthUi extends State { @override initState() { super.initState(); - _authController.addListener(listenOnAuthState); if (widget.errorMessage != null) { errorMessage = widget.errorMessage; } @@ -47,16 +49,9 @@ class _AuthUi extends State { @override void dispose() { loginOngoing.dispose(); - _authController.removeListener(listenOnAuthState); super.dispose(); } - /// Whenever login state is changing, local isLoggedIn - /// listenable value must be updated. - void listenOnAuthState() { - _authController.isLoggedIn().then((value) => isLoggedIn.value = value); - } - @override Widget build(BuildContext context) { final log = Logger(vplanLoggerId); @@ -64,312 +59,250 @@ class _AuthUi extends State { return Scaffold( backgroundColor: PlanColors.AppBackgroundColor, - body: FutureBuilder( - future: _authController.isLoggedIn().then((value) { - log.finer( - "Got a value whether we're logged in: ${value.toString()}"); + body: ValueListenableBuilder( + // rebuilds whenever loginOngoing changes (receives an event) + valueListenable: loginOngoing, + builder: (context, value, child) { if (value) { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => - FlsVplanMainUi(title: AppLocalizations.of(context)!.title)), - ); + return const Center( + child: SizedBox( + height: 80, + width: 80, + child: CircularProgressIndicator())); } - return value; - }), - builder: (context, snapshot) { - return ValueListenableBuilder( - valueListenable: isLoggedIn, - builder: (context, value, child) { - if (!snapshot.hasData) { - return const Center( - child: SizedBox( - height: 80, - width: 80, - child: CircularProgressIndicator())); - } - return ValueListenableBuilder( - // rebuilds whenever loginOngoing changes (receives an event) - valueListenable: loginOngoing, - builder: (context, value, child) { - if (value) { - return const Center( - child: SizedBox( - height: 80, - width: 80, - child: CircularProgressIndicator())); - } - - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only( - left: 30, right: 30, bottom: 15, top: 15), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 15.0), - child: Text(AppLocalizations.of(context)!.title, - style: TextStyle( - color: PlanColors - .PrimaryTextColor, - fontWeight: FontWeight.bold, - fontSize: 32))), - Padding( - padding: const EdgeInsets.only( - top: 15.0, bottom: 15.0), - child: Text( - AppLocalizations.of(context)!.loginDescription, - style: TextStyle( - color: PlanColors - .SecondaryTextColor))), - Padding( - padding: const EdgeInsets.only( - top: 15.0), - child: Text(AppLocalizations.of(context)!.school, - style: TextStyle( - color: PlanColors - .PrimaryTextColor, - fontWeight: FontWeight.bold, - fontSize: 16))), - Divider( - color: PlanColors.BorderColor, - height: 5), - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: List.generate( - Config.schools.length, (index) { - final school = Config.schools - .elementAt(index); - return Container( - padding: const EdgeInsets.only( - left: 5, right: 5), - decoration: BoxDecoration( - color: config.school.id == - school.id - ? PlanColors - .PageIndicatorSelectedColor - : null, - ), - child: IconButton( - icon: Image.asset( - school.assetName), - iconSize: 50, - isSelected: config.school.id == - school.id, - style: const ButtonStyle( - elevation: - MaterialStatePropertyAll( - 2.0), - ), - onPressed: () { - log.fine( - "Selected ${school.name}!"); - setState(() { - config - .setSchool(school.id); - }); - }, - ), - ); - })), - Padding( - padding: const EdgeInsets.only( - top: 30.0), - child: Text(AppLocalizations.of(context)!.credentials, - style: TextStyle( - color: PlanColors - .PrimaryTextColor, - fontWeight: FontWeight.bold, - fontSize: 16))), - Divider( - color: PlanColors.BorderColor, - height: 5), - Padding( - padding: const EdgeInsets.only( - bottom: 10), - child: Text( - errorMessage ?? "", - style: const TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold), - )), - Form( - key: _formKey, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - TextFormField( - autofocus: true, - controller: userName, - decoration: - InputDecoration( - hintText: AppLocalizations.of(context)!.username, - ), - validator: (String? value) { - if (value == null || - value.isEmpty) { - return AppLocalizations.of(context)!.errorUsername; - } - return null; - }, - textInputAction: - TextInputAction.next, - ), - TextFormField( - controller: loginSecret, - obscureText: true, - decoration: - InputDecoration( - hintText: AppLocalizations.of(context)!.password, - ), - validator: (String? value) { - if (value == null || - value.isEmpty) { - return AppLocalizations.of(context)!.errorPassword; - } - return null; - }, - textInputAction: - TextInputAction.done, - onFieldSubmitted: - (value) async { - await _send(); + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only( + left: 30, right: 30, bottom: 15, top: 15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 15.0), + child: Text(AppLocalizations.of(context)!.title, + style: TextStyle( + color: PlanColors.PrimaryTextColor, + fontWeight: FontWeight.bold, + fontSize: 32))), + Padding( + padding: const EdgeInsets.only( + top: 15.0, bottom: 15.0), + child: Text( + AppLocalizations.of(context)! + .loginDescription, + style: TextStyle( + color: PlanColors.SecondaryTextColor))), + Padding( + padding: const EdgeInsets.only(top: 15.0), + child: Text( + AppLocalizations.of(context)!.school, + style: TextStyle( + color: PlanColors.PrimaryTextColor, + fontWeight: FontWeight.bold, + fontSize: 16))), + Divider(color: PlanColors.BorderColor, height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(Config.schools.length, + (index) { + final school = + Config.schools.elementAt(index); + return Container( + padding: const EdgeInsets.only( + left: 5, right: 5), + decoration: BoxDecoration( + color: config.school.id == school.id + ? PlanColors + .PageIndicatorSelectedColor + : null, + ), + child: IconButton( + icon: Image.asset(school.assetName), + iconSize: 50, + isSelected: config.school.id == school.id, + style: const ButtonStyle( + elevation: + MaterialStatePropertyAll(2.0), + ), + onPressed: () { + log.fine("Selected ${school.name}!"); + setState(() { + config.setSchool(school.id); + }); + }, + ), + ); + })), + Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Text( + AppLocalizations.of(context)!.credentials, + style: TextStyle( + color: PlanColors.PrimaryTextColor, + fontWeight: FontWeight.bold, + fontSize: 16))), + Divider(color: PlanColors.BorderColor, height: 5), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + errorMessage ?? "", + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold), + )), + Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + autofocus: true, + controller: userName, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)! + .username, + ), + validator: (String? value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context)! + .errorUsername; + } + return null; + }, + textInputAction: TextInputAction.next, + ), + TextFormField( + controller: loginSecret, + obscureText: true, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)! + .password, + ), + validator: (String? value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context)! + .errorPassword; + } + return null; + }, + textInputAction: TextInputAction.done, + onFieldSubmitted: (value) async { + return _checkFormLoginResult( + _formKey.currentState); + }, + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0), + child: Wrap( + spacing: 5, + runSpacing: 5, + children: [ + ElevatedButton( + onPressed: () async { + return _checkFormLoginResult( + _formKey.currentState); + }, + child: Text( + AppLocalizations.of(context)! + .login), + ), + ElevatedButton.icon( + icon: const Icon( + Icons.qr_code_scanner), + label: Text( + AppLocalizations.of(context)! + .loginCard), + onPressed: _checkLoginCard, + ), + ElevatedButton( + onPressed: () { + context.pushRoute(const AboutUiRoute()); }, - ), - Padding( - padding: const EdgeInsets - .symmetric( - vertical: 16.0), - child: Wrap( - spacing: 5, - runSpacing: 5, - children: [ - ElevatedButton( - onPressed: _send, - child: Text( - AppLocalizations.of(context)!.login), - ), - ElevatedButton.icon( - icon: const Icon(Icons - .qr_code_scanner), - label: Text( - AppLocalizations.of(context)!.loginCard), - onPressed: () async { - final String? - answer = - await Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const AuthScanUi())); - if (answer != null) { - try { - log.finer("Scanned $answer"); - final object = jsonDecode(answer); - userName.value = - TextEditingValue(text: object['clientId']); - loginSecret.value = - TextEditingValue(text: object['clientSecret']); - config.setSchool(object['school']); - if (object['mode'] != null) { - config.setModeString(object['mode']); - } - await _send(); - } on SchoolNotFoundException { - setState(() { - errorMessage = - AppLocalizations.of(context)!.schoolNotSupported; - }); - } on Exception { - setState(() { - errorMessage = - AppLocalizations.of(context)!.invalidBarcodeScanned; - }); - } - } else { - setState(() { - errorMessage = - AppLocalizations.of(context)!.scanCanceled; - }); - } - }, - ), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const AboutUi()), - ); - }, - child: Text( - AppLocalizations.of(context)!.aboutApp)) - ])), - ], - ), - ) - ]))); - }); - }); + child: Text(AppLocalizations.of( + context)! + .aboutApp)) + ])), + ], + ), + ) + ]))); })); } + Future _checkLoginCard() async { + if (context.mounted) { + context.router.push(AuthScanUiRoute(onScanCompleted: (res) { + final log = Logger(vplanLoggerId); + log.finest("Response: $res"); + if (mounted && context.mounted) { + String? localErrorMessage; + if (res.isCancelled()) { + localErrorMessage = AppLocalizations.of(context)?.scanCanceled; + } else if (res.isInvalidBarcode()) { + localErrorMessage = AppLocalizations.of(context)?.invalidBarcodeScanned; + } else if (res.isNoInternet()) { + localErrorMessage = AppLocalizations.of(context)?.loginNotPossibleInternet; + } else if (res.isSchoolNotSupported()) { + localErrorMessage = AppLocalizations.of(context)?.schoolNotSupported; + } + setState(() { + errorMessage = localErrorMessage; + loginOngoing.value = false; + }); + } + })); + } + } + + Future _checkFormLoginResult(FormState? loginCardForm) async { + if (mounted && context.mounted) { + setState(() => loginOngoing.value = true); + } + return _send(loginCardForm).then((value) { + if (mounted && context.mounted) { + if (!value) { + setState(() => loginOngoing.value = false); + } else { + context.navigateTo(const WizardRoute()); + } + } + }); + } + /// Get authentication form details in secret store / configuration /// and trigger login. - Future _send() async { - loginOngoing.value = true; // show spinner + /// TODO: Make similar / unifi with authscanui! + Future _send(FormState? loginCardForm) async { + final log = Logger(vplanLoggerId); final config = Config.getInstance(); - if (_formKey.currentState!.validate()) { + String? localErrorMessage; + bool loginResult = false; + if (!context.mounted) return false; + if (loginCardForm == null || loginCardForm.validate()) { await config.setAuthUser(userName.text); await config.setAuthSecret(loginSecret.text); - if (await _login()) { - // Force reload! - setState(() {}); - } else { - loginOngoing.value = false; // hide spinner - } - } else { - loginOngoing.value = false; // hide spinner - } - } + loginResult = await _authController + .login() + .timeout(const Duration(seconds: 2), onTimeout: () { + if (context.mounted) { + localErrorMessage = AppLocalizations.of(context)?.loginNotPossibleInternet; + } + return false; + }); - /// Execute login and set an error message if - /// required. - Future _login() async { - final log = Logger(vplanLoggerId); - bool before = isLoggedIn.value; - - // login call happens here ↓ - errorMessage = null; - bool loginOK = await _authController - .login() - .timeout(const Duration(seconds: 2), onTimeout: () { - errorMessage = AppLocalizations.of(context)!.loginNotPossibleInternet; - return false; - }); log.finest( - "Login triggered and got result: ${loginOK ? "Perfect!" : "Failed!"}"); - - if (!loginOK && errorMessage == null) { - errorMessage = AppLocalizations.of(context)!.loginNotPossibleCredentials; + "Login triggered and got result: ${loginResult ? "Perfect!" : "Failed!"}"); } - bool after = await _authController.isLoggedIn(); - if (before != after) { - isLoggedIn.value = after; - log.finest( - "Login state changed: ${after ? "Logged in" : "Not logged in"}!"); - return true; + if (mounted && context.mounted && !loginResult) { + if (!loginResult && localErrorMessage == null) { + localErrorMessage = + AppLocalizations.of(context)?.loginNotPossibleCredentials; + } + setState(() => errorMessage = localErrorMessage); } - return false; + + return loginResult; } } diff --git a/lib/ui/flsvplanmainui.dart b/lib/ui/flsvplanmainui.dart index e1be24a881994a00383a16dd8369fa98220664c9..cb646929c55b0058b29baeb7def9f5b67ff2fd53 100644 --- a/lib/ui/flsvplanmainui.dart +++ b/lib/ui/flsvplanmainui.dart @@ -1,16 +1,14 @@ -import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:de_fls_wiesbaden_vplan/ui/settings/plansettingsui.dart'; import 'package:de_fls_wiesbaden_vplan/ui/styles/plancolors.dart'; import 'package:de_fls_wiesbaden_vplan/ui/vplan/planui.dart'; -import 'package:de_fls_wiesbaden_vplan/ui/wizard.dart'; import 'package:de_fls_wiesbaden_vplan/utils/notifications.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; +@RoutePage() class FlsVplanMainUi extends StatefulWidget { - const FlsVplanMainUi({super.key, required this.title}); - - final String title; + const FlsVplanMainUi({super.key}); @override State createState() => _FlsVplanMainUiState(); @@ -26,76 +24,60 @@ class _FlsVplanMainUiState extends State with TickerProviderStat super.initState(); _tabController = TabController(initialIndex: 1, length: 3, vsync: this); } - @override Widget build(BuildContext context) { - final Config config = Config.getInstance(); - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - return FutureBuilder( - future: config.isFirstCall().then((value) async { - if (value) { - await Navigator.push(context,MaterialPageRoute(builder: (context)=>const Wizard())); - } - }), - builder: (context, snapshot) { - BackgroundPush.setupPush(); - if (snapshot.connectionState != ConnectionState.done) { - return const Center(child: CircularProgressIndicator()); - } else { - return Scaffold( - backgroundColor: PlanColors.AppBackgroundColor, - body: SafeArea( - bottom: false, - child: TabBarView( - controller: _tabController, - physics: const NeverScrollableScrollPhysics(), - children: const [ - PlanUi(personalPlan: false), - PlanUi(personalPlan: true), - PlanSettingsUi() - ] + + BackgroundPush.setupPush(); + return Scaffold( + backgroundColor: PlanColors.AppBackgroundColor, + body: SafeArea( + bottom: false, + child: TabBarView( + controller: _tabController, + physics: const NeverScrollableScrollPhysics(), + children: const [ + PlanUi(personalPlan: false), + PlanUi(personalPlan: true), + PlanSettingsUi() + ] + ) + ), + bottomNavigationBar: SafeArea( + top: false, + left: false, + right: false, + child: Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: PlanColors.TabBorderColor, width: 0.5 ) - ), - bottomNavigationBar: SafeArea( - top: false, - left: false, - right: false, - child: Container( - decoration: BoxDecoration( - border: Border( - top: BorderSide( - color: PlanColors.TabBorderColor, width: 0.5 - ) - ) - ), - child: TabBar( - labelColor: PlanColors.SelectedIconColor, - unselectedLabelColor: PlanColors.IconColor, - indicatorColor: Colors.transparent, - indicatorWeight: 0.1, - controller: _tabController, - isScrollable: false, - tabs: [ - Tab( - icon: const Icon(Icons.list), - text: AppLocalizations.of(context)!.schoolPlan, - ), - Tab( - icon: const Icon(Icons.favorite), - text: AppLocalizations.of(context)!.myPlan, - ), - Tab( - icon: const Icon(Icons.settings), - text: AppLocalizations.of(context)!.settings, - ), - ], - ) + ) + ), + child: TabBar( + labelColor: PlanColors.SelectedIconColor, + unselectedLabelColor: PlanColors.IconColor, + indicatorColor: Colors.transparent, + indicatorWeight: 0.1, + controller: _tabController, + isScrollable: false, + tabs: [ + Tab( + icon: const Icon(Icons.list), + text: AppLocalizations.of(context)!.schoolPlan, + ), + Tab( + icon: const Icon(Icons.favorite), + text: AppLocalizations.of(context)!.myPlan, ), + Tab( + icon: const Icon(Icons.settings), + text: AppLocalizations.of(context)!.settings, + ), + ], ) - ); - } - } + ), + ) ); } -} \ No newline at end of file +} diff --git a/lib/ui/helper/exceptions.dart b/lib/ui/helper/exceptions.dart index 2e419fde1b99a42f7a52f2a38ee49837b16c73fd..c45430a23027282724b6e2e33f9c77309a2112e4 100644 --- a/lib/ui/helper/exceptions.dart +++ b/lib/ui/helper/exceptions.dart @@ -8,6 +8,16 @@ class ApiAuthException implements Exception { String toString() => 'ApiAuthException: $message'; } +/// Exception, that connection issues occurred. +class ApiConnectException implements Exception { + final String message; + + ApiConnectException(this.message); + + @override + String toString() => 'ApiConnectException: $message'; +} + /// Exception, that a selected / loaded school /// is not found and not supported. class SchoolNotFoundException implements Exception { diff --git a/lib/ui/helper/types.dart b/lib/ui/helper/types.dart new file mode 100644 index 0000000000000000000000000000000000000000..057939d09ae3bd53f9ad484cea2b41dca5280fa0 --- /dev/null +++ b/lib/ui/helper/types.dart @@ -0,0 +1,18 @@ +class AuthLoginResult { + String result = ''; + + AuthLoginResult(this.result); + + bool isCancelled() { + return result == 'cancelled'; + } + bool isSchoolNotSupported() { + return result == 'school-not-supported'; + } + bool isInvalidBarcode() { + return result == 'invalid-barcode'; + } + bool isNoInternet() { + return result == 'no-internet'; + } +} \ No newline at end of file diff --git a/lib/ui/settings/mainsettings.dart b/lib/ui/settings/mainsettings.dart index ed4e8adbd07f9cc558420656890b2d6bdd865bfb..e810efd3f8c607614207f38290f57311b1803d2e 100644 --- a/lib/ui/settings/mainsettings.dart +++ b/lib/ui/settings/mainsettings.dart @@ -1,5 +1,7 @@ +import 'package:auto_route/auto_route.dart'; import 'package:de_fls_wiesbaden_vplan/controllers/authcontroller.dart'; import 'package:de_fls_wiesbaden_vplan/models/school.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/ui/authui.dart'; import 'package:de_fls_wiesbaden_vplan/ui/helper/consts.dart'; @@ -14,16 +16,15 @@ import 'package:provider/provider.dart'; /// E.g. plan mode /// and offers possibility to log out. class GeneralSettingsUi extends StatefulWidget { + final bool isWizard; + + const GeneralSettingsUi({super.key, this.isWizard = false}); - const GeneralSettingsUi({super.key}); - @override State createState() => _GeneralSettingsUi(); - } class _GeneralSettingsUi extends State { - final AuthController _authController = AuthController.getInstance(); bool listenerAdded = false; @@ -51,106 +52,118 @@ class _GeneralSettingsUi extends State { Widget build(BuildContext context) { final log = Logger(vplanLoggerId); final Config config = context.select((Config ps) => ps); - final School school = config.getSchoolObj(); - + final School school = config.getSchoolObj(); + return FutureBuilder( - future: _authController.isLoggedIn().then((value) { - log.finer("Got a value whether we're logged in: ${value.toString()}"); - if (!value) { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const AuthUi()), - ); - } - return value; - }), - builder: (context, snapshot) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Tooltip( + future: _authController.isLoggedIn().then((value) { + log.finer( + "[mainsettings] Got a value whether we're logged in: ${value.toString()}"); + if (!value) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const AuthUi()), + ); + } + return value; + }), + builder: (context, snapshot) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Tooltip( message: AppLocalizations.of(context)!.helpSettingMode, child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text(AppLocalizations.of(context)!.planModeForm, style: TextStyle(color: PlanColors.PrimaryTextColor, fontWeight: FontWeight.bold),), - Text(AppLocalizations.of(context)!.pupil), - Switch( - // This bool value toggles the switch. - value: config.mode == PlanType.teacher, - activeColor: PlanColors.SelectedIconColor, - inactiveTrackColor: Colors.grey, - onChanged: !config.teacherPermission ? null : (bool value) { - // This is called when the user toggles the switch. - setState(() { - config.setMode(value ? PlanType.teacher : PlanType.pupil); - }); - } - ), - Text(AppLocalizations.of(context)!.teacher), - ], + Text( + AppLocalizations.of(context)!.planModeForm, + style: TextStyle( + color: PlanColors.PrimaryTextColor, + fontWeight: FontWeight.bold), + ), + Text(AppLocalizations.of(context)!.pupil), + Switch( + // This bool value toggles the switch. + value: config.mode == PlanType.teacher, + activeColor: PlanColors.SelectedIconColor, + inactiveTrackColor: Colors.grey, + onChanged: !config.teacherPermission + ? null + : (bool value) { + // This is called when the user toggles the switch. + setState(() { + config.setMode(value + ? PlanType.teacher + : PlanType.pupil); + }); + }), + Text(AppLocalizations.of(context)!.teacher), + ], + ), ), - ), - Tooltip( + Tooltip( message: AppLocalizations.of(context)!.helpSettingRegular, child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text(AppLocalizations.of(context)!.regularPlan, style: TextStyle(color: PlanColors.PrimaryTextColor, fontWeight: FontWeight.bold),), - Switch( - // This bool value toggles the switch. - value: config.addRegularPlan, - activeColor: PlanColors.SelectedIconColor, - onChanged: (bool value) { - // This is called when the user toggles the switch. - setState(() { - config.setAddRegularPlan(value); - }); - } - ) - ], + Text( + AppLocalizations.of(context)!.regularPlan, + style: TextStyle( + color: PlanColors.PrimaryTextColor, + fontWeight: FontWeight.bold), + ), + Switch( + // This bool value toggles the switch. + value: config.addRegularPlan, + activeColor: PlanColors.SelectedIconColor, + onChanged: (bool value) { + // This is called when the user toggles the switch. + setState(() { + config.setAddRegularPlan(value); + }); + }) + ], + ), ), - ), - /* Disabled, as its confusing people. + /* Disabled, as its confusing people. Row(children: [ Text(AppLocalizations.of(context)!.pushNotification, style: TextStyle(color: PlanColors.PrimaryTextColor, fontWeight: FontWeight.bold),), Text( config.notifyRegistered ? AppLocalizations.of(context)!.enabled : AppLocalizations.of(context)!.disabled, style: TextStyle(color: PlanColors.SecondaryTextColor),), ]),*/ - Container( - padding: const EdgeInsets.only(top: 16), - child: Text( - AppLocalizations.of(context)!.school, - style: TextStyle( - color: PlanColors.PrimaryTextColor, fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.left - ) - ), - Divider(color: PlanColors.BorderColor, height: 5), - Container( - padding: const EdgeInsets.only(left: 5, right: 5), - decoration: BoxDecoration( - color: config.school.id == school.id ? PlanColors.PageIndicatorSelectedColor : null, - ), - child: IconButton( - icon: Image.asset(school.assetName), - iconSize: 50, - isSelected: config.school.id == school.id, - style: const ButtonStyle( - elevation: MaterialStatePropertyAll(2.0), + Container( + padding: const EdgeInsets.only(top: 16), + child: Text(AppLocalizations.of(context)!.school, + style: TextStyle( + color: PlanColors.PrimaryTextColor, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.left)), + Divider(color: PlanColors.BorderColor, height: 5), + Container( + padding: const EdgeInsets.only(left: 5, right: 5), + decoration: BoxDecoration( + color: config.school.id == school.id + ? PlanColors.PageIndicatorSelectedColor + : null, ), - onPressed: () { - _logout(context); - }, - ), - ) - ], - ); - } - ); + child: IconButton( + icon: Image.asset(school.assetName), + iconSize: 50, + isSelected: config.school.id == school.id, + style: const ButtonStyle( + elevation: MaterialStatePropertyAll(2.0), + ), + onPressed: () { + _logout(context); + }, + ), + ) + ], + ); + }); } void _logout(BuildContext context) { @@ -163,12 +176,7 @@ class _GeneralSettingsUi extends State { actions: [ // The "Yes" button CupertinoDialogAction( - onPressed: () { - setState(() { - AuthController.getInstance().logout(); - Navigator.of(context).pop(); - }); - }, + onPressed: confirmLogout, isDefaultAction: false, isDestructiveAction: true, child: Text(AppLocalizations.of(context)!.yes), @@ -186,4 +194,14 @@ class _GeneralSettingsUi extends State { ); }); } -} \ No newline at end of file + + Future confirmLogout() async { + await Future.wait([ + AuthController.getInstance().logout(), + Config.getInstance().setFirstCallDone(false) + ]); + if (mounted && context.mounted) { + context.router.replaceAll([AuthUiRoute()]); + } + } +} diff --git a/lib/ui/settings/plansettingsui.dart b/lib/ui/settings/plansettingsui.dart index 083d6aa14e5334150741025dc8ec2c5c7d51d593..0c4b2388cd479cbebb21687e792817a35e89c5ea 100644 --- a/lib/ui/settings/plansettingsui.dart +++ b/lib/ui/settings/plansettingsui.dart @@ -1,12 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:de_fls_wiesbaden_vplan/models/lesson.dart'; import 'package:de_fls_wiesbaden_vplan/models/schoolclass.dart'; import 'package:de_fls_wiesbaden_vplan/models/schooltype.dart'; import 'package:de_fls_wiesbaden_vplan/models/teacher.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/storage/planstorage.dart'; import 'package:de_fls_wiesbaden_vplan/storage/schoolclassstorage.dart'; import 'package:de_fls_wiesbaden_vplan/storage/teacherstorage.dart'; -import 'package:de_fls_wiesbaden_vplan/ui/aboutui.dart'; import 'package:de_fls_wiesbaden_vplan/ui/settings/mainsettings.dart'; import 'package:de_fls_wiesbaden_vplan/ui/styles/plancolors.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -36,10 +37,7 @@ class _PlanSettingsUi extends State { padding: const EdgeInsets.only(top: 15), child: ElevatedButton( onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const AboutUi()), - ); + context.pushRoute(const AboutUiRoute()); }, child: Text(AppLocalizations.of(context)!.aboutApp) ) @@ -51,7 +49,7 @@ class _PlanSettingsUi extends State { child: Text(AppLocalizations.of(context)!.general, style: TextStyle(color: PlanColors.PrimaryTextColor, fontWeight: FontWeight.bold, fontSize: 16)) ), Divider(color: PlanColors.BorderColor, height: 5), - const GeneralSettingsUi() + GeneralSettingsUi(isWizard: widget.isWizard) ]; return Padding( diff --git a/lib/ui/wizard.dart b/lib/ui/wizard.dart index b4ddcbc84ba4cf78ed2dd0cc4acf374a634b48d0..5e0fd987502759a5d0fb56c0a1b219281261facd 100644 --- a/lib/ui/wizard.dart +++ b/lib/ui/wizard.dart @@ -1,4 +1,6 @@ +import 'package:auto_route/auto_route.dart'; import 'package:de_fls_wiesbaden_vplan/controllers/authcontroller.dart'; +import 'package:de_fls_wiesbaden_vplan/routes/routes.gr.dart'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/storage/planstorage.dart'; import 'package:de_fls_wiesbaden_vplan/ui/settings/plansettingsui.dart'; @@ -7,62 +9,60 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +@RoutePage() class Wizard extends StatefulWidget { const Wizard({super.key}); - + @override State createState() => _Wizard(); - } class _Wizard extends State { @override Widget build(BuildContext context) { PlanStorage ps = context.select((PlanStorage ps) => ps); - - return FutureBuilder(future: ps.load(), builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return const Center(child: CircularProgressIndicator()); - } else { - return Scaffold( - backgroundColor: PlanColors.AppBackgroundColor, - body: const SafeArea( - bottom: false, - child: PlanSettingsUi(isWizard: true) - ), - bottomNavigationBar: SafeArea( - top: false, - left: false, - right: false, - child: BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: const Icon(Icons.cancel_outlined), - label: AppLocalizations.of(context)!.cancel, - ), - BottomNavigationBarItem( - icon: const Icon(Icons.navigate_next), - label: AppLocalizations.of(context)!.go - ) - ], - currentIndex: 1, - onTap: (value) async { - if (value == 0) { - await AuthController.getInstance().logout(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - } else { - await Config.getInstance().setFirstCallDone(true); - ps.refresh(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - } - }, - ) - ) - ); - } - }); - } -} \ No newline at end of file + return FutureBuilder( + future: ps.load(), + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const Center(child: CircularProgressIndicator()); + } else { + return Scaffold( + backgroundColor: PlanColors.AppBackgroundColor, + body: const SafeArea( + bottom: false, child: PlanSettingsUi(isWizard: true)), + bottomNavigationBar: SafeArea( + top: false, + left: false, + right: false, + child: BottomNavigationBar( + items: [ + BottomNavigationBarItem( + icon: const Icon(Icons.cancel_outlined), + label: AppLocalizations.of(context)!.cancel, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.navigate_next), + label: AppLocalizations.of(context)!.go) + ], + currentIndex: 1, + onTap: (value) async { + if (value == 0) { + await AuthController.getInstance().logout(); + if (context.mounted) { + context.navigateTo(AuthUiRoute()); + } + } else { + await Config.getInstance().setFirstCallDone(true); + await ps.refresh(); + if (context.mounted) { + context.navigateTo(const FlsVplanMainUiRoute()); + } + } + }, + ))); + } + }); + } +} diff --git a/lib/utils/notifications.dart b/lib/utils/notifications.dart index eb18786dabd635f9625f79567111ac0c033a6814..cbb9dd5b11a4b39e05e8d808a4d342e68b2cecf0 100644 --- a/lib/utils/notifications.dart +++ b/lib/utils/notifications.dart @@ -1,30 +1,36 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:de_fls_wiesbaden_vplan/controllers/authcontroller.dart'; import 'package:de_fls_wiesbaden_vplan/storage/planstorage.dart'; import 'package:de_fls_wiesbaden_vplan/ui/helper/consts.dart'; import 'package:de_fls_wiesbaden_vplan/storage/config.dart'; import 'package:de_fls_wiesbaden_vplan/utils/logger.dart'; +import 'package:flutter/foundation.dart'; import 'package:unifiedpush/unifiedpush.dart'; class BackgroundPush { static void initialize() { final log = getVPlanLogger(); - log.info("BackgroundPush is initializing..."); - - UnifiedPush.initialize( - onNewEndpoint: - onNewEndpoint, // takes (String endpoint, String instance) in args - onRegistrationFailed: onRegistrationFailed, // takes (String instance) - onUnregistered: onUnregistered, // takes (String instance) - onMessage: pushNotifyReceived, // takes (String message, String instance) in args - ); + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { + log.info("BackgroundPush is initializing..."); + UnifiedPush.initialize( + onNewEndpoint: + onNewEndpoint, // takes (String endpoint, String instance) in args + onRegistrationFailed: onRegistrationFailed, // takes (String instance) + onUnregistered: onUnregistered, // takes (String instance) + onMessage: pushNotifyReceived, // takes (String message, String instance) in args + ); + } else { + log.info("BackgroundPush disabled as its not supported on this platform..."); + } } static Future setupPush() async { final log = getVPlanLogger(); + if (kIsWeb || (!Platform.isAndroid && !Platform.isIOS)) { + return; + } log.fine("Setup background push"); if (!await AuthController.getInstance().isLoggedIn()) { return; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a75ce44aa57281e14a1f67a30fc43eba2c4ae922..3ea8893a97b9fe97faa2a940367de3b0089f765e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import flutter_local_notifications import flutter_secure_storage_macos +import mobile_scanner import package_info_plus import path_provider_foundation import share_plus @@ -16,6 +17,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) diff --git a/metadata/en-US/changelogs/30007.txt b/metadata/en-US/changelogs/30007.txt new file mode 100644 index 0000000000000000000000000000000000000000..ea70b2931efc12ab6546e6cd6eab5135ecd0df02 --- /dev/null +++ b/metadata/en-US/changelogs/30007.txt @@ -0,0 +1,3 @@ +Features: + - Sort classes + - Refresh UI on resume of the app diff --git a/metadata/en-US/changelogs/30008.txt b/metadata/en-US/changelogs/30008.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae24c3bb4e1a5f7f86ac808d74390e3ad87b870e --- /dev/null +++ b/metadata/en-US/changelogs/30008.txt @@ -0,0 +1,2 @@ +Bugfix: + - Switched barcode library due to crashes. \ No newline at end of file diff --git a/metadata/en-US/changelogs/30009.txt b/metadata/en-US/changelogs/30009.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d73c1e9e5bf92faff2a284f0a43cab283ea0461 --- /dev/null +++ b/metadata/en-US/changelogs/30009.txt @@ -0,0 +1,2 @@ +Bugfix: + - Fixing black screen during login. \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index adc44a3d9fd24106dd4bb81ce2bde7be729b4b88..52ded57666170cb449bce64379e5921fa115cbb2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "67.0.0" add_2_calendar: dependency: "direct main" description: name: add_2_calendar - sha256: dbcd0bf296fbbe00861a6f101af8cdb3c163a8c3ff5d3c99a4b081c2f37c724f + sha256: "8d7a82aba607d35f2a5bc913419e12f865a96a350a8ad2509a59322bc161f200" url: "https://pub.dev" source: hosted - version: "2.2.5" + version: "3.0.1" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.4.1" args: dependency: transitive description: @@ -41,6 +41,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: "82f8df1d177416bc6b7a449127d0270ff1f0f633a91f2ceb7a85d4f07c3affa1" + url: "https://pub.dev" + source: hosted + version: "7.8.4" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: "11067a3bcd643812518fe26c0c9ec073990286cabfd9d74b6da9ef9b913c4d22" + url: "https://pub.dev" + source: hosted + version: "7.3.2" boolean_selector: dependency: transitive description: @@ -77,26 +93,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.8" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -109,10 +125,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.1" characters: dependency: transitive description: @@ -141,18 +157,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -165,10 +181,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + sha256: "74a4727e030347edff3b6e5256b7fb0c3de8af8ed278e6c56718760786a1fa40" url: "https://pub.dev" source: hosted - version: "0.3.3+6" + version: "0.3.4" crypto: dependency: transitive description: @@ -189,18 +205,18 @@ packages: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.4" dbus: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" + version: "0.7.10" dependency_validator: dependency: transitive description: @@ -229,10 +245,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -258,18 +274,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.0.1" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - sha256: "501ed9d54f1c8c0535b7991bade36f9e7e3b45a2346401f03775c1ec7a3c06ae" + sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3 url: "https://pub.dev" source: hosted - version: "15.1.2" + version: "16.3.2" flutter_local_notifications_linux: dependency: transitive description: @@ -295,10 +311,10 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "9.0.0" flutter_secure_storage_linux: dependency: transitive description: @@ -335,18 +351,18 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -385,10 +401,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -445,14 +461,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" localstorage: dependency: "direct main" description: @@ -481,42 +521,50 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3" + url: "https://pub.dev" + source: hosted + version: "3.5.7" mockito: dependency: "direct dev" description: name: mockito - sha256: "7d5b53bcd556c1bc7ffbe4e4d5a19c3e112b7e925e9e172dd7c6ad0630812616" + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" url: "https://pub.dev" source: hosted - version: "5.4.2" + version: "5.4.4" nested: dependency: transitive description: @@ -553,10 +601,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -569,26 +617,26 @@ packages: dependency: transitive description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -601,10 +649,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -617,26 +665,26 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.8" pool: dependency: transitive description: @@ -669,22 +717,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" - qr_code_scanner: - dependency: "direct main" - description: - name: qr_code_scanner - sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd - url: "https://pub.dev" - source: hosted - version: "1.0.1" share_plus: dependency: "direct main" description: name: share_plus - sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd + sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900" url: "https://pub.dev" source: hosted - version: "7.2.1" + version: "7.2.2" share_plus_platform_interface: dependency: transitive description: @@ -713,10 +753,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -729,18 +769,18 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -774,10 +814,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_span: dependency: transitive description: @@ -798,18 +838,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -838,10 +878,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timezone: dependency: transitive description: @@ -878,10 +918,10 @@ packages: dependency: transitive description: name: unifiedpush_android - sha256: "19fcdd2671c46bd074efbb80c43cedd0bcddd1fc0cfd3e2f74aec03fb0659d58" + sha256: "610ad746294541f56d632adf9afba5d1c164c44e23ec0dd2162a41a6ff00a00e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.3" unifiedpush_platform_interface: dependency: transitive description: @@ -894,34 +934,34 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.5" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -934,58 +974,58 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" uuid: dependency: transitive description: name: uuid - sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.3.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -994,6 +1034,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" watcher: dependency: transitive description: @@ -1006,26 +1054,26 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.5.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.4" win32: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.2.0" workmanager: dependency: "direct main" description: @@ -1038,18 +1086,18 @@ packages: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -1059,5 +1107,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.2 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 73dd9d1eec7a3bb4f00c2a42cbd5f32ac13b2ab0..27142bb78c1fbc63e32fd3b83205de0fc3397e66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 3.0.7+30007 +version: 3.0.9+3000902 environment: sdk: '>=2.19.4 <3.0.0' @@ -41,16 +41,25 @@ dependencies: provider: ^6.0.5 localstore: ^1.3.5 localstorage: ^4.0.1+2 - flutter_secure_storage: ^8.0.0 + flutter_secure_storage: ^9.0.0 intl: ^0.18.0 dots_indicator: ^3.0.0 flutter_svg: ^2.0.4 workmanager: ^0.5.1 - flutter_local_notifications: ^15.1.0+1 + flutter_local_notifications: ^16.2.0 share_plus: ^7.0.2 - add_2_calendar: ^2.2.3 - # mobile_scanner: ^3.2.0 > anti-feature (uses ml-kit) - qr_code_scanner: ^1.0.1 # maintenance mode + add_2_calendar: ^3.0.1 + # for scanning login cards, we need a QR scanner. + # We really would love to see a free library, which is not + # depending on google or firebase. Unfortunately, its pretty + # difficult. The qr_code_scanner is in maintenance mode and + # the last tests like: + # - https://github.com/juliuscanute/qr_code_scanner/issues/633 + # - https://github.com/juliuscanute/qr_code_scanner/issues/719 + # That means for now, when using mobile_scanner, we really have + # a problem to be available in projects like F-Droid. + mobile_scanner: ^3.5.0 # > anti-feature (uses ml-kit) + #qr_code_scanner: ^1.0.1 # maintenance mode; has some ugly bugs. logging: ^1.1.1 package_info_plus: ^4.0.2 url_launcher: ^6.1.10 @@ -58,6 +67,7 @@ dependencies: flutter_localizations: sdk: flutter jwt_decoder: ^2.0.1 + auto_route: ^7.8.4 dev_dependencies: flutter_test: @@ -68,9 +78,10 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 mockito: ^5.4.0 build_runner: ^2.3.3 + auto_route_generator: ^7.3.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/fetch_schoolclassstorage_test.mocks.dart b/test/fetch_schoolclassstorage_test.mocks.dart index 66f740b49fded3410d2a37435043ba482d89c733..a41456ff64950bd825e7dbfc7ad67371a0b09f4e 100644 --- a/test/fetch_schoolclassstorage_test.mocks.dart +++ b/test/fetch_schoolclassstorage_test.mocks.dart @@ -1,19 +1,22 @@ -// Mocks generated by Mockito 5.4.0 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in de_fls_wiesbaden_vplan/test/fetch_schoolclassstorage_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'dart:convert' as _i4; -import 'dart:typed_data' as _i5; +import 'dart:typed_data' as _i6; import 'package:http/http.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors @@ -70,6 +73,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> get( Uri? url, { @@ -90,6 +94,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> post( Uri? url, { @@ -120,6 +125,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> put( Uri? url, { @@ -150,6 +156,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> patch( Uri? url, { @@ -180,6 +187,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> delete( Uri? url, { @@ -210,6 +218,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future read( Uri? url, { @@ -221,10 +230,18 @@ class MockClient extends _i1.Mock implements _i2.Client { [url], {#headers: headers}, ), - returnValue: _i3.Future.value(''), + returnValue: _i3.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), ) as _i3.Future); + @override - _i3.Future<_i5.Uint8List> readBytes( + _i3.Future<_i6.Uint8List> readBytes( Uri? url, { Map? headers, }) => @@ -234,8 +251,9 @@ class MockClient extends _i1.Mock implements _i2.Client { [url], {#headers: headers}, ), - returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)), - ) as _i3.Future<_i5.Uint8List>); + returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i3.Future<_i6.Uint8List>); + @override _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) => (super.noSuchMethod( @@ -252,6 +270,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.StreamedResponse>); + @override void close() => super.noSuchMethod( Invocation.method( diff --git a/test/fetch_teacher_test.mocks.dart b/test/fetch_teacher_test.mocks.dart index a11a1320d7eeeb25f56c49e07985a62ac68ca8b5..c6cda88e8b323b148bddb8fcb706b6ce1925d8bd 100644 --- a/test/fetch_teacher_test.mocks.dart +++ b/test/fetch_teacher_test.mocks.dart @@ -1,19 +1,22 @@ -// Mocks generated by Mockito 5.4.0 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in de_fls_wiesbaden_vplan/test/fetch_teacher_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'dart:convert' as _i4; -import 'dart:typed_data' as _i5; +import 'dart:typed_data' as _i6; import 'package:http/http.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors @@ -70,6 +73,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> get( Uri? url, { @@ -90,6 +94,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> post( Uri? url, { @@ -120,6 +125,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> put( Uri? url, { @@ -150,6 +156,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> patch( Uri? url, { @@ -180,6 +187,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future<_i2.Response> delete( Uri? url, { @@ -210,6 +218,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.Response>); + @override _i3.Future read( Uri? url, { @@ -221,10 +230,18 @@ class MockClient extends _i1.Mock implements _i2.Client { [url], {#headers: headers}, ), - returnValue: _i3.Future.value(''), + returnValue: _i3.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), ) as _i3.Future); + @override - _i3.Future<_i5.Uint8List> readBytes( + _i3.Future<_i6.Uint8List> readBytes( Uri? url, { Map? headers, }) => @@ -234,8 +251,9 @@ class MockClient extends _i1.Mock implements _i2.Client { [url], {#headers: headers}, ), - returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)), - ) as _i3.Future<_i5.Uint8List>); + returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i3.Future<_i6.Uint8List>); + @override _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) => (super.noSuchMethod( @@ -252,6 +270,7 @@ class MockClient extends _i1.Mock implements _i2.Client { ), )), ) as _i3.Future<_i2.StreamedResponse>); + @override void close() => super.noSuchMethod( Invocation.method( diff --git a/web/index.html b/web/index.html index 756638a928937c583d5932d79fa6fa0260856584..e15ae6317533ef8888256a5f59e9b47c138089ec 100644 --- a/web/index.html +++ b/web/index.html @@ -31,6 +31,7 @@ FLS VPlan App +